The-Simpsons-Hit-and-Run/game/code/camera/railcam.cpp

1261 lines
39 KiB
C++

//=============================================================================
// Copyright (C) 2002 Radical Entertainment Ltd. All rights reserved.
//
// File: RailCam.cpp
//
// Description: Implement RailCam
//
// History: 17/07/2002 + Created -- Cary Brisebois (Borrowed and adapted from J-L Duprat)
//
//=============================================================================
//========================================
// System Includes
//========================================
// Foundation Tech
#include <raddebug.hpp>
#include <raddebugwatch.hpp>
//========================================
// Project Includes
//========================================
#ifndef WORLD_BUILDER
#include <camera/RailCam.h>
#include <camera/isupercamtarget.h>
#include <camera/supercamcontroller.h>
#include <camera/supercamconstants.h>
#include <debug/debuginfo.h>
#include <memory/classsizetracker.h>
#include <p3d/pointcamera.hpp>
//TODO: I only really need the tuner variables... Break this file up.
#include <worldsim/character/character.h>
#else
#include "RailCam.h"
#include "isupercamtarget.h"
#include "supercamcontroller.h"
#include "supercamconstants.h"
#include "../../../tools/globalcode/utility/GLExt.h"
#include <maya/mpoint.h>
class tPointCamera
{
public:
void GetFOV( float* fov, float* aspect ) { *fov = 1.5707f; *aspect = 4.0f / 3.0f; };
};
namespace CharacterTune
{
static float sfMaxSpeed;
static float sfDashBurstMax;
};
#endif
#ifdef DEBUGWATCH
float RAIL_CAM_MIN_FOV = SUPERCAM_DEFAULT_MIN_FOV;
#else
const float RAIL_CAM_MIN_FOV = SUPERCAM_DEFAULT_MIN_FOV;
#endif
const float RAIL_CAM_FOV_LAG = SUPERCAM_DEFAULT_FOV_LAG;
//******************************************************************************
//
// Global Data, Local Data, Local Classes
//
//******************************************************************************
const float DEFAULT_MIN_RADIUS = 6.0f;
const float DEFAULT_MAX_RADIUS = 12.0f;
const char* const RailCam::BehaviourNames[] = { "Distance", "Projection" };
#ifdef DEBUGWATCH
float MAX_STEP = 0.25f;
#else
const float MAX_STEP = 0.25f;
#endif
//******************************************************************************
//
// Public Member Functions
//
//******************************************************************************
//==============================================================================
// RailCam::RailCam
//==============================================================================
// Description: Constructor.
//
// Parameters: None.
//
// Return: N/A.
//
//==============================================================================
RailCam::RailCam() :
mTarget( NULL ),
mBehaviour( PROJECTION ),
mMinRadius( DEFAULT_MIN_RADIUS ),
mMaxRadius( DEFAULT_MAX_RADIUS ),
mTrackDist( 0.0f ),
mStartU( -1.0f ),
mU( 0.0f ),
mStep( MAX_STEP ),
mPositionLag( 0.04f ),
mTargetLag( 0.04f ),
mFOVDelta( 0.0f ),
mMaxFOV( 0.0f ),
mFOVLag( RAIL_CAM_FOV_LAG ),
mTrackRail( false ),
mReverseSensing( false ),
mDrawRail( false ),
mDrawHull( false ),
mDrawCylinder( false ),
mDrawIntersections( false ),
mAllowUpdate( true ),
mReset( false ),
mResetting( false )
{
CLASSTRACKER_CREATE( RailCam );
mQ.SetBasis( rmt::Spline::BSpline );
mQd.SetBasis( rmt::Spline::DBSpline );
mTargetOffset.Set( 0.0f, 0.0f, 0.0f );
mPosition.Set( 0.0f, 0.0f, 0.0f );
mPositionDelta.Set( 0.0f, 0.0f, 0.0f );
mTargetPosition.Set( 0.0f, 0.0f, 0.0f );
mTargetPositionDelta.Set( 0.0f, 0.0f, 0.0f );
}
//==============================================================================
// RailCam::~RailCam
//==============================================================================
// Description: Destructor.
//
// Parameters: None.
//
// Return: N/A.
//
//==============================================================================
RailCam::~RailCam()
{
CLASSTRACKER_DESTROY( RailCam );
}
//=============================================================================
// RailCam::OnInit
//=============================================================================
// Description: Comment
//
// Parameters: ()
//
// Return: void
//
//=============================================================================
void RailCam::OnInit()
{
//Override the Near plane...
SetNearPlane( mMinRadius );
InitMyController();
if ( !mReset )
{
//This is to make the cameras move to a better place before interpolation.
DenyUpdate();
DoFirstTime();
DoCameraCut();
Update( 16 );
AllowUpdate();
}
else
{
mResetting = true;
}
}
//=============================================================================
// RailCam::OnShutdown
//=============================================================================
// Description: Comment
//
// Parameters: ()
//
// Return: void
//
//=============================================================================
void RailCam::OnShutdown()
{
//Reset me to the origin of the spline.
mU = 0.0f;
}
//=============================================================================
// RailCam::Update
//=============================================================================
// Description: Comment
//
// Parameters: ( unsigned int milliseconds )
//
// Return: void
//
//=============================================================================
void RailCam::Update( unsigned int milliseconds )
{
float timeMod = milliseconds / 16.0f;
rmt::Vector targetPos;
GetTargetPosition( &targetPos, true );
// rmt::Vector camPos;
// GetPosition( &camPos );
rmt::Vector desiredPos;
if ( mResetting )
{
mU = 0.0f;
mResetting = false;
}
// compute camera position
switch(mBehaviour)
{
case DISTANCE:
default:
desiredPos = FindCameraPosition_Distance(targetPos, mMaxRadius);
break;
case PROJECTION:
desiredPos = FindCameraPosition_Projection(targetPos, mMaxRadius);
break;
}
rmt::Vector desiredTarget;
desiredTarget = FindCameraLookAt( targetPos, desiredPos );
bool cut, firstTime;
cut = GetFlag( (Flag)CUT );
firstTime = GetFlag( (Flag)FIRST_TIME );
if ( cut || firstTime )
{
//Reset the smoothing stuff.
mPosition = desiredPos;
mPositionDelta.Set( 0.0f, 0.0f, 0.0f );
mTargetPosition = desiredTarget;
mTargetPositionDelta.Set( 0.0f, 0.0f, 0.0f );
//SetFOV( mMaxFOV );
mFOVDelta = 0.0f;
if ( cut )
{
if ( GetFlag( (Flag)START_TRANSITION ) )
{
SetFlag( (Flag)START_TRANSITION, false );
}
else if ( GetFlag( (Flag)TRANSITION ) )
{
SetFlag( (Flag)END_TRANSITION, true );
}
SetFlag( (Flag)CUT, false );
}
else
{
//Not cutting, so let's try this.
float fov, aspect = 0.0f;
GetCamera()->GetFOV( &fov, &aspect );
if ( mAllowUpdate )
{
SetFOV( fov );
}
}
SetFlag( (Flag)FIRST_TIME, false );
}
else
{
//Let's smooth out the motion...
float posLag = mPositionLag * timeMod;
CLAMP_TO_ONE(posLag);
MotionCubic( &mPosition.x, &mPositionDelta.x, desiredPos.x, posLag );
MotionCubic( &mPosition.y, &mPositionDelta.y, desiredPos.y, posLag );
MotionCubic( &mPosition.z, &mPositionDelta.z, desiredPos.z, posLag );
float targLag = mTargetLag * timeMod;
CLAMP_TO_ONE(targLag);
MotionCubic( &mTargetPosition.x, &mTargetPositionDelta.x, desiredTarget.x, targLag );
MotionCubic( &mTargetPosition.y, &mTargetPositionDelta.y, desiredTarget.y, targLag );
MotionCubic( &mTargetPosition.z, &mTargetPositionDelta.z, desiredTarget.z, targLag );
}
//--------- Goofin' with the FOV
if ( GetFlag( (Flag)TRANSITION ) || mTarget->IsCar() )
{
if ( mAllowUpdate )
{
SetFOV( mMaxFOV );
}
mFOVDelta = 0.0f; //reset
}
else
{
#ifndef WORLD_BUILDER
float zoom = mController->GetValue( SuperCamController::zToggle );
#else
float zoom = 0.0f;
#endif
float FOV = GetFOV();
FilterFov( zoom, RAIL_CAM_MIN_FOV, mMaxFOV, FOV, mFOVDelta, mFOVLag, timeMod );
if ( mAllowUpdate )
{
SetFOV( FOV );
}
}
//--------- Set values.
if ( mAllowUpdate )
{
SetCameraValues( milliseconds, mPosition, mTargetPosition );
}
DrawRail( true );
DrawHull( true );
DrawCylinder( targetPos );
}
//=============================================================================
// RailCam::InitController
//=============================================================================
// Description: Comment
//
// Parameters: ()
//
// Return: void
//
//=============================================================================
void RailCam::InitController()
{
}
//=============================================================================
// RailCam::LoadSettings
//=============================================================================
// Description: Comment
//
// Parameters: ( unsigned char* settings )
//
// Return: void
//
//=============================================================================
void RailCam::LoadSettings( unsigned char* settings )
{
}
//******************************************************************************
//
// Private Member Functions
//
//******************************************************************************
//=============================================================================
// RailCam::OnRegisterDebugControls
//=============================================================================
// Description: Comment
//
// Parameters: ()
//
// Return: void
//
//=============================================================================
void RailCam::OnRegisterDebugControls()
{
#ifdef DEBUGWATCH
char nameSpace[256];
sprintf( nameSpace, "SuperCam\\Player%d\\Rail", GetPlayerID() );
radDbgWatchAddUnsignedInt( ((unsigned int*)(&mBehaviour)), "Behaviour", nameSpace, NULL, NULL, RailCam::DISTANCE, RailCam::PROJECTION );
radDbgWatchAddFloat( &mMinRadius, "Min. Radius", nameSpace, NULL, NULL, 0.0f, 12.0f );
radDbgWatchAddFloat( &mMaxRadius, "Max. Radius", nameSpace, NULL, NULL, 0.0f, 12.0f );
radDbgWatchAddBoolean( &mTrackRail, "Track Rail", nameSpace, NULL, NULL );
radDbgWatchAddFloat( &mTrackDist, "Track Distance", nameSpace, NULL, NULL, -10.0f, 10.0f );
radDbgWatchAddBoolean( &mReverseSensing, "Reverse Sense", nameSpace, NULL, NULL );
radDbgWatchAddVector( &mTargetOffset.x, "Target Offset", nameSpace, NULL, NULL, -5.0f, 5.0f );
radDbgWatchAddVector( &mAxisPlay.x, "Axis Play", nameSpace, NULL, NULL, 0.0f, rmt::PI_BY2 );
radDbgWatchAddBoolean( &mDrawRail, "Draw Rails", nameSpace, NULL, NULL );
radDbgWatchAddBoolean( &mDrawHull, "Draw Hulls", nameSpace, NULL, NULL );
radDbgWatchAddBoolean( &mDrawCylinder, "Draw Cylinder", nameSpace, NULL, NULL );
radDbgWatchAddBoolean( &mDrawIntersections, "Draw Intersections", nameSpace, NULL, NULL );
radDbgWatchAddFloat( &MAX_STEP, "Max Step", nameSpace, NULL, NULL, 0.0f, 1.0f );
radDbgWatchAddFloat( &mPositionLag, "Position Lag Factor", nameSpace, NULL, NULL, 0.0f, 1.0f );
radDbgWatchAddFloat( &mTargetLag, "Target Lag Factor", nameSpace, NULL, NULL, 0.0f, 1.0f );
radDbgWatchAddFloat( &mFOVLag, "FOV Lag", nameSpace, NULL, NULL, 0.0f, 1.0f );
radDbgWatchAddFloat( &RAIL_CAM_MIN_FOV, "FOV Min", nameSpace, NULL, NULL, 0.0f, rmt::PI_BY2 );
#endif
}
//=============================================================================
// RailCam::OnUnregisterDebugControls
//=============================================================================
// Description: Comment
//
// Parameters: ()
//
// Return: void
//
//=============================================================================
void RailCam::OnUnregisterDebugControls()
{
#ifdef DEBUGWATCH
radDbgWatchDelete( &mBehaviour );
radDbgWatchDelete( &mMinRadius );
radDbgWatchDelete( &mMaxRadius );
radDbgWatchDelete( &mTrackRail );
radDbgWatchDelete( &mTrackDist );
radDbgWatchDelete( &mReverseSensing );
radDbgWatchDelete( &mTargetOffset.x );
radDbgWatchDelete( &mAxisPlay.x );
radDbgWatchDelete( &mDrawRail );
radDbgWatchDelete( &mDrawHull );
radDbgWatchDelete( &mDrawCylinder );
radDbgWatchDelete( &mDrawIntersections );
radDbgWatchDelete( &MAX_STEP );
radDbgWatchDelete( &mPositionLag );
radDbgWatchDelete( &mTargetLag );
radDbgWatchDelete( &RAIL_CAM_MIN_FOV );
radDbgWatchDelete( &mFOVLag );
#endif
}
//=============================================================================
// RailCam::GetTargetSpeedModifier
//=============================================================================
// Description: Comment
//
// Parameters: ()
//
// Return: float
//
//=============================================================================
float RailCam::GetTargetSpeedModifier()
{
rmt::Vector vel;
mTarget->GetVelocity( &vel );
float speed = vel.Magnitude(); //Square root!
//TODO:I wish this was a const.
if ( speed < CharacterTune::sfMaxSpeed || rmt::Epsilon( speed, CharacterTune::sfMaxSpeed, 0.01f ) )
{
return 1.0f;
}
float maxMod = CharacterTune::sfMaxSpeed + CharacterTune::sfDashBurstMax / CharacterTune::sfMaxSpeed;
float modifier = (speed - CharacterTune::sfMaxSpeed) / CharacterTune::sfDashBurstMax * maxMod;
rAssert( modifier > 0.01f );
return modifier;
}
//=============================================================================
// RailCam::GetTargetPosition
//=============================================================================
// Description: Comment
//
// Parameters: ( rmt::Vector* position, bool withOffset )
//
// Return: void
//
//=============================================================================
void RailCam::GetTargetPosition( rmt::Vector* position,
bool withOffset )
{
rAssert( mTarget );
mTarget->GetPosition( position );
}
//=============================================================================
// RailCam::IntervalClamp
//=============================================================================
// Description: Comment
//
// Parameters: ( float &t )
//
// Return: SolutionType
//
//=============================================================================
RailCam::SolutionType RailCam::IntervalClamp( float &t ) const
{
if(t>1.0f)
{
t=1.0f;
return WORSTCASE;
}
if(t<0.0f)
{
t=0.0f;
return WORSTCASE;
}
return APPROX;
}
//=============================================================================
// RailCam::ProjectPointOnLine
//=============================================================================
// Description: Comment
//
// Parameters: (const rmt::Vector& A, const rmt::Vector& B, const rmt::Vector& O, float& t)
//
// Return: float
//
//=============================================================================
float& RailCam::ProjectPointOnLine(const rmt::Vector& A, const rmt::Vector& B, const rmt::Vector& O, float& t) const
{
// Projects the point O on the line that goes through A and B
// Caller needs to clamp the parameter t such that it always lies between A and B
if( A == B )
t = 0.0f; // not really a segment...
else
t = -((B.x-A.x)*(A.x-O.x) + (B.y-A.y)*(A.y-O.y) + (B.z-A.z)*(A.z-O.z))/((B.x-A.x)*(B.x-A.x) + (B.y-A.y)*(B.y-A.y) + (B.z-A.z)*(B.z-A.z));
return t;
}
//=============================================================================
// RailCam::IntersectLineCylinder
//=============================================================================
// Description: Comment
//
// Parameters: (const int segment, const rmt::Vector& origin, const float radius, const rmt::Vector& neighbour, float& t)
//
// Return: RailCam
//
//=============================================================================
RailCam::SolutionType RailCam::IntersectLineCylinder(const int segment, const rmt::Vector& origin, const float radius, const rmt::Vector& neighbour, float& t)
{
// Intersects the line segment defined by the knots at segment and segment+1 with a
// infinitely long cylinder centered on origin and of specified radius. If there are
// multiple solutions (potentially an infinity), pick the one closest to neighbour.
// Returns true if there is a solution and t is set to the parametric position of intersection
// along the line segment... If there is no solution, return false and t is set to the closest
// point to the cylinder along the line segment
// To simplify the math, we consider that the spline segments are straight lines between the knots
// we return the t at which the intersection occured on the line, but camera will be moved to that
// parametric position along the curve.
// vertical axis of cylinder is Y
rmt::Vector A = mQ.Evaluate(float(segment));
rmt::Vector B = mQ.Evaluate(float(segment+1));
if(rmt::Epsilon(B.x, A.x) && rmt::Epsilon(B.z, A.z))
{
// curve segment is parallel to cylinder (zero or infinity of intersections)
// project origin on segment instead
ProjectPointOnLine(A, B, origin, t);
rmt::Vector L(A.x, origin.y, A.z);
if(rmt::Epsilon(L.Magnitude(), radius))
return EXACT; // edge is along the cylinder
else
{
return IntervalClamp(t); // edge is inside the cylinder
}
}
// There are 0, 1 or 2 intersections along the segment
// Replace the parametrized line equation into the equation for the cylinder
// and solve for the parameter t. The algebra is ugly, but here goes: We are
// solving a quadratic and use the expected names for variables...
float a = (A.x-B.x)*(A.x-B.x)+(A.z-B.z)*(A.z-B.z);
float b = 2.0f* (- A.x*A.x - A.z*A.z + A.x*B.x + A.z*B.z + A.x*origin.x - B.x*origin.x + A.z*origin.z - B.z*origin.z);
float c = (A.x-origin.x)*(A.x-origin.x)+(A.z-origin.z)*(A.z-origin.z)-radius*radius;
float b2m4ac = b*b-4.0f*a*c;
if(b2m4ac>0.0f)
{
float t1 = (-b+rmt::Sqrt(b2m4ac))/(2.0f*a);
float t2 = (-b-rmt::Sqrt(b2m4ac))/(2.0f*a);
if((t1<0.0f || t1>1.0f) && (t2<0.0f || t2>1.0f))
{
// both intersections are outside of the segment, project origin on segment
return IntervalClamp(ProjectPointOnLine(A, B, origin, t));
}
if(t1<0.0f || t1>1.0f)
{
// only t2 is on the segment
t = t2;
return EXACT;
}
if(t2<0.0f || t2>1.0f)
{
// only t1 is on the segment
t = t1;
return EXACT;
}
// we have two intersections on the segment, pick the one closest to neighbour
rmt::Vector X1;
X1.Interpolate(A, B, t1);
rmt::Vector L;
L.Sub(neighbour, X1);
float l1 = L.MagnitudeSqr();
rmt::Vector X2;
X2.Interpolate(A, B, t2);
L.Sub(neighbour, X2);
float l2 = L.MagnitudeSqr();
if(l1<l2)
t = t1; // X1 is closer
else
t = t2; // X2 is closer
return EXACT;
}
else
{
// no intersection, project origin on segment
return IntervalClamp(ProjectPointOnLine(A, B, origin, t));
}
}
//=============================================================================
// RailCam::FindCameraPosition_Distance
//=============================================================================
// Description: Comment
//
// Parameters: (const rmt::Vector& target, const float radius)
//
// Return: rmt
//
//=============================================================================
rmt::Vector RailCam::FindCameraPosition_Distance(const rmt::Vector& target, const float radius)
{
// finds the position of camera along rail where the camera is at distance radius
// from origin (target) and closest to prevRailPos. Each segment provides a candidate and the closest
// one to previous camera position is used...
if(mQ.GetNumSegments()==0)
return rmt::Vector(0.0f, 0.0f, 0.0f);
unsigned int i;
for(i=0; i<3; i++)
mCandidates[i].Reset();
if(mReverseSensing)
{
mU=mQ.GetEndParam()-mU; // we need to know where the camera really is for the evaluation
}
rmt::Vector prevRailPos;
// if( GetFlag( (Flag)CUT ) || GetFlag( (Flag)FIRST_TIME ) )
// {
// // when initializing camera minimize distance to actor
// prevRailPos = target;
// }
// else
// {
// // when not initializing camera we want to minize movement between frames
prevRailPos = mQ.Evaluate(mU); // previous camera position on the rail
// }
for(i=0; i<mQ.GetNumSegments(); i++)
{
float segT = 0.0f;
// each segment will return the best it can do (best solution type would be APPROX in this case)
SolutionType index = IntersectLineCylinder(i, target, radius, prevRailPos, segT);
rmt::Vector p=mQ.Evaluate(i+segT);
rmt::Vector delta(p.x-prevRailPos.x, p.y-prevRailPos.y, p.z-prevRailPos.z);
float len = delta.MagnitudeSqr();
#ifdef DEBUGINFO_ENABLED
if( mDrawIntersections )
{
// mark the actual intersection of the cylinder and the spline curve
tColour col;
if(EXACT==index)
col.Set(255,255,255); // this should not happen in corridors
else if(APPROX==index)
col.Set(255,255,0);
else
col.Set(255,0,0);
if ( DEBUGINFO_PUSH_SECTION( "Rail Cam" ) )
{
DEBUGINFO_ADDSTAR( p, col, 0.5f );
}
DEBUGINFO_POP_SECTION();
}
#endif
if(len<mCandidates[index].dist)
{
mCandidates[index].dist = len;
mCandidates[index].segment = i;
mCandidates[index].u = segT;
mCandidates[index].pu = p;
mCandidates[index].pDist = rmt::Abs(segT+i-mU);
if(mQ.GetClosed() && mCandidates[index].pDist>mQ.GetEndParam()/2.0f)
mCandidates[index].pDist = rmt::Abs(mCandidates[index].pDist-mQ.GetEndParam()); // its shorter going the other way!
}
}
rmt::Vector railPosition;
// Decide whether to use the exact or approx solution to minimize camera travel
if(mCandidates[EXACT].segment != -1 && mCandidates[EXACT].pDist>mStep)
{
// closest intersection point is quite far from current parametric position and would cause camera to jump.
// if approximate solution is closer use that instead...
if(mCandidates[APPROX].pDist<=mStep)
{
railPosition = FinalizeRailPosition(APPROX);
}
else
{
// Pick the closest (parameter space) point and move towards it.
SolutionType index = (mCandidates[EXACT].pDist<=mCandidates[APPROX].pDist)?EXACT:APPROX;
railPosition = FinalizeRailPosition(index);
// we never considered WorstCase solutions
}
}
else if(mCandidates[EXACT].segment != -1)
{
// use the proper intersection, its close enough
railPosition = FinalizeRailPosition(EXACT);
}
else if(mCandidates[APPROX].segment != -1)
{
// use the closest approximation
railPosition = FinalizeRailPosition(APPROX);
}
else
railPosition = FinalizeRailPosition(WORSTCASE);
return railPosition;
}
//=============================================================================
// RailCam::FindCameraPosition_Projection
//=============================================================================
// Description: Comment
//
// Parameters: (const rmt::Vector& target, const float pOffset)
//
// Return: rmt
//
//=============================================================================
rmt::Vector RailCam::FindCameraPosition_Projection(const rmt::Vector& target, const float pOffset)
{
// finds the position of camera along rail where the camera is at fixed parametric distance from
// from projection of target on the rail. Each segment provides a candidate projection and the closest
// one to previous camera position is used...
if(mQ.GetNumSegments()==0)
return rmt::Vector(0.0f, 0.0f, 0.0f);
unsigned int i;
for(i=0; i<3; i++)
mCandidates[i].Reset();
if( mReverseSensing )
mU=mQ.GetEndParam()-mU; // we need to know where the camera really is for the evaluation
rmt::Vector prevRailPos;
if( GetFlag( (Flag)CUT ) || GetFlag( (Flag)FIRST_TIME ) )
{
// when initializing camera minimize distance to actor
prevRailPos = target;
}
else
{
// when not initializing camera we want to minize movement between frames
prevRailPos = mQ.Evaluate(mU); // previous camera position on the rail
}
for(i=0; i<mQ.GetNumSegments(); i++)
{
float segT = 0.0f;
// each segment will return the best it can do
SolutionType index = IntervalClamp(ProjectPointOnLine(mQ.GetKnot(i), mQ.GetKnot(i+1), target, segT));
float curveT = i + segT + pOffset;
if(curveT < 0.0f)
{
if(mQ.GetClosed())
while(curveT<0.0f) curveT += mQ.GetEndParam();
else
curveT = 0.0f;
}
if(curveT > mQ.GetEndParam())
{
if(mQ.GetClosed())
while(curveT>mQ.GetEndParam()) curveT -= mQ.GetEndParam();
else
curveT = 0.0f;
}
rmt::Vector p=mQ.Evaluate(curveT);
//rmt::Vector delta(p.x-prevRailPos.x, p.y-prevRailPos.y, p.z-prevRailPos.z);
rmt::Vector delta( p.x-target.x, p.y-target.y, p.z-target.z);
float len = delta.MagnitudeSqr();
#ifdef DEBUGINFO_ENABLED
if( mDrawIntersections )
{
// mark the actual intersection of the cylinder and the spline curve
tColour col;
if(EXACT==index)
col.Set(255,255,255); // this should not happen in corridors
else if(APPROX==index)
col.Set(255,255,0);
else
col.Set(255,0,0);
if ( DEBUGINFO_PUSH_SECTION( "Rail Cam" ) )
{
DEBUGINFO_ADDSTAR( mQ.Evaluate(i+segT), col, 0.5f );
}
DEBUGINFO_POP_SECTION();
}
#endif
if(len<mCandidates[index].dist)
{
mCandidates[index].dist = len;
mCandidates[index].segment = int(rmt::Floor(curveT));
mCandidates[index].u = curveT-rmt::Floor(curveT);
mCandidates[index].pu = p;
mCandidates[index].pDist = rmt::Abs(curveT-mU);
if(mQ.GetClosed() && mCandidates[index].pDist>mQ.GetEndParam()/2.0f)
mCandidates[index].pDist = rmt::Abs(mCandidates[index].pDist-mQ.GetEndParam()); // its shorter going the other way!
}
}
// TODO: this could be bad because if the second intersection is a better choice than
// the best approximation it will be ignored...
rmt::Vector railPosition;
// Decide whether to use the exact or approx solution to minimize camera travel
if(mCandidates[EXACT].segment != -1 && mCandidates[EXACT].pDist>mStep)
{
// closest intersection point is quite far from current parametric position and would cause camera to jump.
// if approximate solution is closer use that instead...
if(mCandidates[APPROX].pDist<=mStep)
{
railPosition = FinalizeRailPosition(APPROX);
}
else
{
// Pick the closest (parameter space) point and move towards it.
SolutionType index = (mCandidates[EXACT].pDist<=mCandidates[APPROX].pDist)?EXACT:APPROX;
railPosition = FinalizeRailPosition(index);
// we never considered WorstCase solutions
}
}
else if(mCandidates[EXACT].segment != -1)
{
// use the proper intersection, its close enough
railPosition = FinalizeRailPosition(EXACT);
}
else if(mCandidates[APPROX].segment != -1)
{
// use the closest approximation
railPosition = FinalizeRailPosition(APPROX);
}
else
railPosition = FinalizeRailPosition(WORSTCASE);
return railPosition;
}
//=============================================================================
// RailCam::FinalizeRailPosition
//=============================================================================
// Description: Comment
//
// Parameters: (SolutionType index)
//
// Return: rmt
//
//=============================================================================
rmt::Vector RailCam::FinalizeRailPosition(SolutionType index)
{
rAssert(mCandidates[index].segment!=-1);
if ( GetFlag( (Flag)CUT ) )
{
// when we are cutting to a camera we can jump straight to the parametric position selected
mU = mCandidates[index].u+mCandidates[index].segment;
if(mReverseSensing)
{
mU=mQ.GetEndParam()-mU; // reverse camera if needed
return mQ.Evaluate(mU);
}
else
{
return mCandidates[index].pu;
}
}
//if(mCandidates[index].pDist>0.5f)
//{
// if(mReverseSensing)
// mU=mQ.GetEndParam()-mU; // reverse camera if needed
// return mQ.Evaluate(mU); // target is too far along rail, just stay put.
//}
// TODO: take care of acceleration for start/stop on the rail
if(mCandidates[index].pDist<=mStep)
{
// target is within step size, snap to it
mU = mCandidates[index].u+mCandidates[index].segment;
if(mReverseSensing)
{
mU=mQ.GetEndParam()-mU; // reverse camera if needed
return mQ.Evaluate(mU);
}
else
{
return mCandidates[index].pu;
}
return mCandidates[index].pu;
}
// target is too far, walk towards it
if(mCandidates[index].u+mCandidates[index].segment>mU)
mU+=mStep;
else
mU-=mStep;
if(mReverseSensing)
mU=mQ.GetEndParam()-mU; // reverse camera if needed
return mQ.Evaluate(mU);
}
//=============================================================================
// RailCam::FindCameraLookAt
//=============================================================================
// Description: Comment
//
// Parameters: (const rmt::Vector& target, const rmt::Vector& desiredPos)
//
// Return: rmt
//
//=============================================================================
rmt::Vector RailCam::FindCameraLookAt(const rmt::Vector& target, const rmt::Vector& desiredPos)
{
rmt::Vector lookAt(target);
if(mTrackRail && rmt::Abs(mTrackDist)>0.001f)
{
float trackU = mU + mTrackDist;
lookAt = TestEval( trackU );
}
/*
#ifdef DEBUGINFO_ENABLED
if(mTrackRail && mDrawRail)
{
if ( DEBUGINFO_PUSH_SECTION( "Rail Cam" ) )
{
DEBUGINFO_ADDLINE(mQ.Evaluate(trackU), lookAt, tColour(0,0,255));
DEBUGINFO_ADDSTAR(lookAt, tColour(0,0,255), 0.5f);
}
DEBUGINFO_POP_SECTION();
}
#endif
*/
//Take the offset and apply it to the look at.
//If the trackDist is 0, then do a little track dist anyway and use it to calculate the heading.
if ( mTargetOffset.x != 0 ||
mTargetOffset.y != 0 ||
mTargetOffset.z != 0 )
{
rmt::Vector offset;
//Make a transformation that puts the offset into the rotation space of
//the camera target.
offset = lookAt - desiredPos;
rmt::Vector vup = UpdateVUP( desiredPos, lookAt );
rmt::Matrix mat;
mat.Identity();
mat.FillHeading( offset, vup );
//reuse offset.
offset = mTargetOffset;
offset.Transform( mat );
lookAt.Add( offset );
CorrectDist( desiredPos, lookAt );
}
return lookAt;
}
//=============================================================================
// RailCam::TestEval
//=============================================================================
// Description: Comment
//
// Parameters: ( float u )
//
// Return: rmt
//
//=============================================================================
rmt::Vector RailCam::TestEval( float u )
{
rmt::Vector lookAt;
if(u > mQ.GetEndParam())
{
// past end of curve
if(mQ.GetClosed())
{
u -= mQ.GetEndParam(); // cycle on closed curve
lookAt = mQ.Evaluate(u);
}
else
{
// Interpolate along curve tangent at endpoint
float mult = rmt::Floor(u/mQ.GetEndParam());
u -= mult*mQ.GetEndParam(); // distance beyond end of curve
// if curve has triple end-knots, derivative there is zero so move in...
float evalAt=mQ.GetEndParam();
int index = mQ.GetNumVertices();
if(mQ.GetCntrlVertex(index-1) == mQ.GetCntrlVertex(index-2) &&
mQ.GetCntrlVertex(index-2) == mQ.GetCntrlVertex(index-3))
evalAt -= 1.0f;
rmt::Vector p = mQ.Evaluate(mQ.GetEndParam()); // point at the end of the spline
rmt::Vector t = mQd.Evaluate(evalAt); // tangent at the end of the spline
t.Scale(u);
lookAt.Add(p, t);
}
}
else if(u < 0.0f)
{
// before begining of curve
if(mQ.GetClosed())
{
u += mQ.GetEndParam(); // cycle on closed curve
lookAt = mQ.Evaluate(u);
}
else
{
// Interpolate along curve tangent at endpoint
float mult = rmt::Floor(-u/mQ.GetEndParam());
u += mult*mQ.GetEndParam(); // distance beyond end of curve
// if curve has triple end-knots, derivative there is zero so move in...
float evalAt=0.0f;
if(mQ.GetCntrlVertex(0) == mQ.GetCntrlVertex(1) &&
mQ.GetCntrlVertex(1) == mQ.GetCntrlVertex(2))
evalAt = 1.0f;
rmt::Vector p = mQ.Evaluate(0.0f); // point at the end of the spline
rmt::Vector t = mQd.Evaluate(evalAt); // tangent at the end of the spline
t.Scale(u);
lookAt.Add(p, t);
}
}
else
{
// along the curve this is easy
lookAt = mQ.Evaluate(u);
}
return lookAt;
}
//=============================================================================
// RailCam::DrawRail
//=============================================================================
// Description: Comment
//
// Parameters: (bool active)
//
// Return: void
//
//=============================================================================
void RailCam::DrawRail(bool active)
{
#ifdef DEBUGINFO_ENABLED
if(!mDrawRail)
return; // not set to draw
if ( DEBUGINFO_PUSH_SECTION( "Rail Cam" ) )
{
tColour cRail;
if(active)
cRail.Set(255,255,255);
else
cRail.Set(255,255,0);
// spline path
const unsigned int numSteps = 10;
rmt::Vector p0, p1;
p0=mQ.InitForwardDifferencing(numSteps);
unsigned int j;
for(j=0; j<numSteps*mQ.GetNumSegments(); j++)
{
p1=mQ.Forward();
DEBUGINFO_ADDLINE(p0, p1, cRail);
if(j%numSteps==0)
DEBUGINFO_ADDSTAR(p0, cRail, 0.5f);
p0=p1;
}
DEBUGINFO_ADDSTAR(p1, cRail, 0.5f);
}
DEBUGINFO_POP_SECTION();
#endif
}
//=============================================================================
// RailCam::DrawHull
//=============================================================================
// Description: Comment
//
// Parameters: (bool active)
//
// Return: void
//
//=============================================================================
void RailCam::DrawHull(bool active)
{
#ifdef DEBUGINFO_ENABLED
if(!mDrawHull)
return; // not set to draw
if ( DEBUGINFO_PUSH_SECTION( "Rail Cam" ) )
{
tColour cHull;
if(active)
cHull.Set(255,255,0);
else
cHull.Set(255,0,0);
int numCVs = mQ.GetNumVertices();
int i;
for(i=1; i<numCVs; i++)
{
DEBUGINFO_ADDSTAR(mQ.GetCntrlVertex(i-1), cHull, 0.5f);
DEBUGINFO_ADDLINE(mQ.GetCntrlVertex(i-1), mQ.GetCntrlVertex(i), cHull);
}
DEBUGINFO_ADDSTAR(mQ.GetCntrlVertex(numCVs-1), cHull, 0.5f);
if(mQ.GetClosed())
DEBUGINFO_ADDLINE(mQ.GetCntrlVertex(0), mQ.GetCntrlVertex(numCVs-1), cHull); // close the hull
}
DEBUGINFO_POP_SECTION();
#endif
}
//=============================================================================
// RailCam::DrawCylinder
//=============================================================================
// Description: Comment
//
// Parameters: ( const rmt::Vector& origin)
//
// Return: void
//
//=============================================================================
void RailCam::DrawCylinder( const rmt::Vector& origin)
{
#ifdef DEBUGINFO_ENABLED
if(!mDrawCylinder)
return; // not set to draw
if ( DEBUGINFO_PUSH_SECTION( "Rail Cam" ) )
{
tColour cCyln(255,255,0);
rmt::Vector bottomC(origin);
bottomC.y -= 1.2f;
rmt::Vector topC(origin);
topC.y += 2.0f;
// bottom cap
//g_pDebug->AddCircle(bottomC, m_minRadius, cCyln);
DEBUGINFO_ADDCIRCLE(bottomC, mMaxRadius, cCyln);
DEBUGINFO_ADDCIRCLE(bottomC, mMinRadius, cCyln);
// top cap
//g_pDebug->AddCircle(topC, m_minRadius, cCyln);
DEBUGINFO_ADDCIRCLE(topC, mMaxRadius, cCyln);
DEBUGINFO_ADDCIRCLE(topC, mMinRadius, cCyln);
// sides
int max = 8;
int i;
for(i=0; i<max; i++)
{
rmt::Vector A(bottomC.x+mMaxRadius*rmt::Cos(rmt::DegToRadian(360.0f/max*i)), bottomC.y, bottomC.z+mMaxRadius*rmt::Sin(rmt::DegToRadian(360.0f/max*i)));
rmt::Vector B(topC.x+mMaxRadius*rmt::Cos(rmt::DegToRadian(360.0f/max*i)), topC.y, topC.z+mMaxRadius*rmt::Sin(rmt::DegToRadian(360.0f/max*i)));
DEBUGINFO_ADDLINE(A, B, cCyln);
}
}
DEBUGINFO_POP_SECTION();
#endif
#ifdef WORLD_BUILDER
GLExt::drawSphere( mMaxRadius * 100.0f, MPoint( 0, 0, 0 ) );
#endif
}
//=============================================================================
// RailCam::GetWatcherName
//=============================================================================
// Description: the name of the class for the watcher or other debug purposes
//
// Parameters: NONE
//
// Return: const char* - the name of the class
//
//=============================================================================
#ifdef DEBUGWATCH
const char* RailCam::GetWatcherName() const
{
return "RailCam";
}
#endif