Files
expvintl 419f2e4752 init
2025-02-23 17:40:52 +08:00

1555 lines
52 KiB
C++

/////////////////////////////////////////////////////////////////////////////////
// FILE : racingline.cpp
// PURPOSE :
// AUTHOR :
// CREATED : 29/03/10
/////////////////////////////////////////////////////////////////////////////////
#include "vehicleAi/racingline.h"
// Framework headers
#include "fwmaths/Angle.h"
#include "debug/DebugScene.h"
#include "vehicleAi/pathfind.h"
AI_OPTIMISATIONS()
//---------------------------------------------------------------------------
// Racing line base class.
//---------------------------------------------------------------------------
RacingLine::RacingLine()
{
;
}
RacingLine::~RacingLine()
{
;
}
void RacingLine::ClearAll(void)
{
m_roughPathPolyLine.clear();
m_smoothedPathPolyLine.clear();
}
void RacingLine::ClearStampedData(u32 updateStamp)
{
while(m_roughPathPolyLine.size() && m_roughPathPolyLine[0].m_second == updateStamp)
{
//m_roughPathPolyLine.pop_front();
m_roughPathPolyLine.Delete(0);
}
while(m_smoothedPathPolyLine.size() && m_smoothedPathPolyLine[0].m_second == updateStamp)
{
//m_smoothedPathPolyLine.pop_front();
m_smoothedPathPolyLine.Delete(0);
}
}
void RacingLine::RoughPathPointAdd(u32 updateStamp, const Vector3& newRoughPathPoint)
{
const int roughPathPointCount = m_roughPathPolyLine.size();
if((roughPathPointCount == 0) || ((newRoughPathPoint - m_roughPathPolyLine[roughPathPointCount - 1].m_first).Mag() > 0.01f))
{
PointWithUpdateStamp pointWithUpdateStamp(newRoughPathPoint, updateStamp);
//m_roughPathPolyLine.push_back(pointWithUpdateStamp);
m_roughPathPolyLine.PushAndGrow(pointWithUpdateStamp);
}
}
Vector3 RacingLine::RoughPathPointGetLast(void)
{
//return m_roughPathPolyLine.get_back().m_first;
return m_roughPathPolyLine.back().m_first;
}
bool RacingLine::SmoothedPathInit(int numSmoothPathSegsPerRoughSeg)
{
m_smoothedPathPolyLine.clear();
m_numSmoothPathSegsPerRoughSeg = numSmoothPathSegsPerRoughSeg;
return true;
}
void RacingLine::SmoothedPathAdvance(int numSmoothPathSegsPerRoughSeg)
{
// Check if we need to re-init the smoothed path.
if(numSmoothPathSegsPerRoughSeg != m_numSmoothPathSegsPerRoughSeg)
{
SmoothedPathInit(numSmoothPathSegsPerRoughSeg);
}
}
void RacingLine::SmoothedPathUpdate(void)
{
;
}
Vector3 RacingLine::SmoothedPathGetPointPos(int pathPointIndex) const
{
Assert(pathPointIndex < (int)m_smoothedPathPolyLine.size());
return m_smoothedPathPolyLine[pathPointIndex].m_first;
}
void RacingLine::SmoothedPathGetPointAngleAndRadiusOfCurvature(int smoothedPathPointIndex, float& angle_out, float& radius_out) const
{
const int smoothedPathPointCount = m_smoothedPathPolyLine.size();
Assert(smoothedPathPointIndex <= smoothedPathPointCount);
// Get or create the 3 associated node positions.
Vector3 nodeAPos;
Vector3 nodeBPos;
Vector3 nodeCPos;
if(smoothedPathPointIndex == 0)
{
nodeBPos = m_smoothedPathPolyLine[smoothedPathPointIndex].m_first;
nodeCPos = m_smoothedPathPolyLine[smoothedPathPointIndex + 1].m_first;
const Vector3 nodeBCDelta = nodeCPos - nodeBPos;
nodeAPos = nodeBPos - nodeBCDelta;
}
else if(smoothedPathPointIndex < (smoothedPathPointCount - 1))
{
nodeAPos = m_smoothedPathPolyLine[smoothedPathPointIndex - 1].m_first;
nodeBPos = m_smoothedPathPolyLine[smoothedPathPointIndex].m_first;
nodeCPos = m_smoothedPathPolyLine[smoothedPathPointIndex + 1].m_first;
}
else
{
nodeAPos = m_smoothedPathPolyLine[smoothedPathPointIndex - 1].m_first;
nodeBPos = m_smoothedPathPolyLine[smoothedPathPointIndex].m_first;
const Vector3 nodeABDelta = nodeBPos - nodeAPos;
nodeCPos = nodeBPos + nodeABDelta;
}
Assert(Vec3IsValid(nodeAPos));
Assert(Vec3IsValid(nodeBPos));
Assert(Vec3IsValid(nodeCPos));
// Get the angle between the nodes.
angle_out = 0.0f;
{
Vector2 springADir((nodeBPos - nodeAPos), Vector2::kXY);
Assert(Vec2IsValid(springADir));
springADir.Normalize();
Assert(Vec2IsValid(springADir));
Assert(springADir.Mag() > 0.99f);
Vector2 springBDir((nodeCPos - nodeBPos), Vector2::kXY);
Assert(Vec2IsValid(springBDir));
springBDir.Normalize();
Assert(Vec2IsValid(springBDir));
Assert(springBDir.Mag() > 0.99f);
const Vector2 springBSideRightDir(springBDir.y, -springBDir.x);
Assert(Vec2IsValid(springBSideRightDir));
const float nextSpringsDirAlignmentForward = springBDir.Dot(springADir);
const float nextSpringsDirAlignmentRightward = springBSideRightDir.Dot(springADir);
Assert(!(nextSpringsDirAlignmentForward == 0.0f && nextSpringsDirAlignmentRightward == 0.0f));
angle_out = rage::Atan2f(-nextSpringsDirAlignmentRightward, nextSpringsDirAlignmentForward);
angle_out = fwAngle::LimitRadianAngle(angle_out);
}
if( (rage::Abs(angle_out) < 0.0001f) ||
(rage::Abs(angle_out) > (PI - 0.0001f)))
{
radius_out = 10000000.0f;// A really big radius.
}
else
{
// Formula from http://en.wikipedia.org/wiki/Radius.
radius_out = rage::Abs((nodeAPos - nodeCPos).Mag() / (2.0f * rage::Sinf(rage::Abs(angle_out))));
}
Assert(radius_out >= 0.0f);
}
void RacingLine::SmoothedPathGetPointAngleAndRadiusOfCurvature(int smoothedPathPointIndex, float smoothedPathSegmentPortion, float& angle_out, float& radius_out) const
{
const int smoothedPathPointCount = m_smoothedPathPolyLine.size();
Assert(smoothedPathPointIndex <= smoothedPathPointCount);
Assert((smoothedPathSegmentPortion >= 0.0f) && (smoothedPathSegmentPortion <= 1.0f));
if(smoothedPathPointIndex == (smoothedPathPointCount - 1))
{
SmoothedPathGetPointAngleAndRadiusOfCurvature(smoothedPathPointIndex, angle_out, radius_out);
}
else
{
float angleA = 0.0f;
float radiusA = 0.0f;
SmoothedPathGetPointAngleAndRadiusOfCurvature(smoothedPathPointIndex, angleA, radiusA);
float angleB = 0.0f;
float radiusB = 0.0f;
SmoothedPathGetPointAngleAndRadiusOfCurvature(smoothedPathPointIndex + 1, angleB, radiusB);
angle_out = Lerp(smoothedPathSegmentPortion, angleA, angleB);
radius_out = Lerp(smoothedPathSegmentPortion, radiusA, radiusB);
}
}
float RacingLine::SmoothedPathGetPointVelPortionMaxPropagated(int smoothedPathPointIndex, float smoothedPathSegmentPortion, float vehVelMax, float vehSideAccMax, float ASSERT_ONLY(vehStartAccMax), float vehStopAccMax)
{
const int smoothedPathPointCount = m_smoothedPathPolyLine.size();
Assert(smoothedPathPointIndex <= smoothedPathPointCount);
Assert((smoothedPathSegmentPortion >= 0.0f) && (smoothedPathSegmentPortion <= 1.0f));
Assert(vehVelMax >= 0.0f);
Assert(vehSideAccMax >= 0.0f);
Assert(vehStartAccMax >= 0.0f);
Assert(vehStopAccMax >= 0.0f);
if(smoothedPathPointCount == 0)
{
return 0.0f;
}
//atArray<float> maxVelUnclampedAtNodes;
//atArray<float> distToNextNodes;
m_maxVelUnclampedAtNodes.clear();
m_distToNextNodes.clear();
for(int i = 0; i < smoothedPathPointCount; ++i)
{
float nodeBAngle = 0.0f;
float nodeBRadius = 0.0f;
SmoothedPathGetPointAngleAndRadiusOfCurvature(i, nodeBAngle, nodeBRadius);
const float nodeBVel2MaxUnclamped = (nodeBRadius * vehSideAccMax);
Assert(nodeBVel2MaxUnclamped >= 0.0f);
const float nodeBVelMaxUnclamped = rage::Sqrtf(nodeBVel2MaxUnclamped);
m_maxVelUnclampedAtNodes.PushAndGrow(nodeBVelMaxUnclamped);
float distToNextNode = 0.0f;
if(i < (smoothedPathPointCount - 1))
{
distToNextNode = (m_smoothedPathPolyLine[i + 1].m_first - m_smoothedPathPolyLine[i].m_first).Mag();
}
else
{
// Just use the same value as the last link.
distToNextNode = (m_smoothedPathPolyLine[smoothedPathPointCount - 1].m_first - m_smoothedPathPolyLine[smoothedPathPointCount - 2].m_first).Mag();
}
m_distToNextNodes.PushAndGrow(distToNextNode);
}
// Propagate the max decelerations backwards (this assures we are never going too fast to make the upcoming turns).
for(int i = (smoothedPathPointCount - 1); i > 0; --i)
{
// The total braking acceleration over the available distance between the nodes must
// be taken into account.
const float currentNodesMaxVel = m_maxVelUnclampedAtNodes[i];
const float prevNodesMaxVel = m_maxVelUnclampedAtNodes[i - 1];
const float distBetweenNodes = m_distToNextNodes[i - 1];
const float prevNodesNewMaxVel2ToMakeNextNode = ((currentNodesMaxVel * currentNodesMaxVel) + (2.0f * vehStopAccMax * distBetweenNodes));
Assert(prevNodesNewMaxVel2ToMakeNextNode >= 0.0f);
const float prevNodesNewMaxVelToMakeNextNode = rage::Sqrtf(prevNodesNewMaxVel2ToMakeNextNode);
if(prevNodesNewMaxVelToMakeNextNode < prevNodesMaxVel)
{
m_maxVelUnclampedAtNodes[i - 1] = prevNodesNewMaxVelToMakeNextNode;
}
}
// // Propagate the max accelerations forwards (this assures we are always going as fast as we can).
// for(int i = 0; i < (smoothedPathPointCount - 1); ++i)
// {
// // The total start acceleration over the available distance between the nodes must
// // be taken into account.
// const float currentNodesMaxVel = maxVelUnclampedAtNodes[i];
// const float nextNodesMaxVel = maxVelUnclampedAtNodes[i + 1];
// const float distBetweenNodes = distToNextNodes[i];
// const float nextNodesNewMaxVel2 = ((currentNodesMaxVel * currentNodesMaxVel) + (2.0f * vehStartAccMax * distBetweenNodes));
// Assert(nextNodesNewMaxVel2 >= 0.0f);
// const float nextNodesNewMaxVel = rage::Sqrtf(nextNodesNewMaxVel2);
// if(nextNodesNewMaxVel < nextNodesMaxVel)
// {
// maxVelUnclampedAtNodes[i + 1] = nextNodesNewMaxVel;
// }
// }
// Get the vel portion at the requested location along the path.
float nodeAVelMaxUnclamped = m_maxVelUnclampedAtNodes[smoothedPathPointIndex];
float nodeAVelMaxClamped = rage::Clamp(nodeAVelMaxUnclamped, 0.0f, vehVelMax);
float nodeAVelMaxPortion = (nodeAVelMaxClamped / vehVelMax);
if(smoothedPathPointIndex < (smoothedPathPointCount - 1))
{
float nodeBVelMaxUnclamped = m_maxVelUnclampedAtNodes[smoothedPathPointIndex + 1];
float nodeBVelMaxClamped = rage::Clamp(nodeBVelMaxUnclamped, 0.0f, vehVelMax);
float nodeBVelMaxPortion = (nodeBVelMaxClamped / vehVelMax);
float velMaxPortionInterpolated = Lerp(smoothedPathSegmentPortion, nodeAVelMaxPortion, nodeBVelMaxPortion);
return velMaxPortionInterpolated;
}
else
{
return nodeAVelMaxPortion;
}
}
Vector3 RacingLine::SmoothedPathGetPosAlong(int startPathPointIndex, float startPathSegmentPortion, float lookAheadDist) const
{
const int smoothedPathPointCount = m_smoothedPathPolyLine.size();
Assert(startPathPointIndex <= smoothedPathPointCount);
Assert((startPathSegmentPortion >= 0.0f) && (startPathSegmentPortion <= 1.0f));
Assert(lookAheadDist >= 0.0f);
if(smoothedPathPointCount == 0)
{
return Vector3(0.0f, 0.0f, 0.0f);
}
if(startPathPointIndex >= (smoothedPathPointCount - 1))
{
return m_smoothedPathPolyLine[smoothedPathPointCount - 1].m_first;
}
Vector3 pathPointAPos = m_smoothedPathPolyLine[startPathPointIndex].m_first;
Vector3 pathPointBPos = m_smoothedPathPolyLine[startPathPointIndex + 1].m_first;
Vector3 pathPointABDelta = (pathPointBPos - pathPointAPos);
float pathSegmentLength = pathPointABDelta.Mag();
const float pathSegmentPortionRemaining = (1.0f - startPathSegmentPortion);
const float pathSegmentLengthRemaining = pathSegmentPortionRemaining * pathSegmentLength;
if(lookAheadDist < pathSegmentLengthRemaining)
{
const float pathSegmentLengthUsed = pathSegmentLengthRemaining - lookAheadDist;
const float pathSegmentPortionUsed = pathSegmentLengthUsed / pathSegmentLength;
const float pathSegmentPortionOfEndPoint = startPathSegmentPortion + pathSegmentPortionUsed;
const Vector3 posOnPath(pathPointAPos + (pathPointABDelta * pathSegmentPortionOfEndPoint));
return posOnPath;
}
float lengthSoFar = pathSegmentLengthRemaining;
for(int i = startPathPointIndex + 1; i < smoothedPathPointCount - 1; ++i)
{
pathPointAPos = m_smoothedPathPolyLine[i].m_first;
pathPointBPos = m_smoothedPathPolyLine[i + 1].m_first;
pathPointABDelta = (pathPointBPos - pathPointAPos);
pathSegmentLength = pathPointABDelta.Mag();
const float lengthToGo = lookAheadDist - lengthSoFar;
if(lengthToGo <= pathSegmentLength)
{
const float pathSegmentPortionOfEndPoint = lengthToGo / pathSegmentLength;
const Vector3 posOnPath(pathPointAPos + (pathPointABDelta * pathSegmentPortionOfEndPoint));
return posOnPath;
}
lengthSoFar += pathSegmentLength;
}
// We've gone to the end of the smoothed path so just return the end point.
return m_smoothedPathPolyLine[smoothedPathPointCount - 1].m_first;
}
Vector3 RacingLine::SmoothedPathGetClosestPos(const Vector3& pos, int& pathPointClosest_out, float& pathSegmentPortion_out) const
{
const int smoothedPathPointCount = m_smoothedPathPolyLine.size();
float pathPointSegClosestDist = 100000.0f;// Large sentinel value.
Vector3 closestPos(0.0f, 0.0f, 0.0f);
bool pointFound = false;
for(int i = 0; i < (smoothedPathPointCount - 1); ++i)
{
Vector3 pathPointAPos = m_smoothedPathPolyLine[i].m_first;
Vector3 pathPointBPos = m_smoothedPathPolyLine[i + 1].m_first;
// Find the closest position to this pathPoint and portion along it's segment to the next pathPoint.
float segmentPortion = 0.0f;
Vector3 closestPointOnSegmentFlat = GetClosestPointOnSegmentFlat(pathPointAPos, pathPointBPos, pos, segmentPortion);
// Check if this is the closest one so far.
Vector3 posFlat = pos;
posFlat.z = 0.0f;
const float distToSeg = (posFlat - closestPointOnSegmentFlat).Mag();
if(distToSeg < pathPointSegClosestDist)
{
pointFound = true;
pathPointClosest_out = i;
pathSegmentPortion_out = segmentPortion;
pathPointSegClosestDist = distToSeg;
closestPos = pathPointAPos + ((pathPointBPos - pathPointAPos) * segmentPortion);
}
}
//if we haven't found something on the smoothed path, try the rough
if(pointFound == false)
{
const int roughPathPointCount = m_roughPathPolyLine.size();
if(roughPathPointCount > 2)
{
for(int i = 0; i < (roughPathPointCount - 1); ++i)
{
Vector3 pathPointAPos = m_roughPathPolyLine[i].m_first;
Vector3 pathPointBPos = m_roughPathPolyLine[i + 1].m_first;
// Find the closest position to this pathPoint and portion along it's segment to the next pathPoint.
float segmentPortion = 0.0f;
Vector3 closestPointOnSegmentFlat = GetClosestPointOnSegmentFlat(pathPointAPos, pathPointBPos, pos, segmentPortion);
// Check if this is the closest one so far.
Vector3 posFlat = pos;
posFlat.z = 0.0f;
const float distToSeg = (posFlat - closestPointOnSegmentFlat).Mag();
if(distToSeg < pathPointSegClosestDist)
{
pathPointClosest_out = i;
pathSegmentPortion_out = segmentPortion;
pathPointSegClosestDist = distToSeg;
closestPos = pathPointAPos + ((pathPointBPos - pathPointAPos) * segmentPortion);
}
}
}
else if(roughPathPointCount > 0)
{
closestPos = m_roughPathPolyLine[roughPathPointCount-1].m_first;//just aim for the furthest node we have, it'll be ok
}
}
return closestPos;
}
void RacingLine::Draw( float DEV_ONLY(drawVertOffset),
Color32 DEV_ONLY(roughPathPointsColour),
Color32 DEV_ONLY(smoothPathColour),
bool DEV_ONLY(drawPathAsHeath),
float DEV_ONLY(vehVelMax),
float DEV_ONLY(vehSideAccMax),
float DEV_ONLY(vehStartAccMax),
float DEV_ONLY(vehStopAccMax)
) const
{
#if __DEV
RoughPathPointsDraw(drawVertOffset, roughPathPointsColour);
if(drawPathAsHeath)
{
SmoothedPathDrawHeat(drawVertOffset, vehVelMax, vehSideAccMax, vehStartAccMax, vehStopAccMax);
}
else
{
SmoothedPathDraw(drawVertOffset, smoothPathColour);
}
#endif // __DEV
}
#if __DEV
void RacingLine::RoughPathPointsDraw(float drawVertOffset, Color32 roughPathPointsColour) const
{
// Draw the rough path points.
const int roughPathPointCount = m_roughPathPolyLine.size();
Vec3V drawVertOffsetV(0.0f, 0.0f, drawVertOffset);
for(int i = 0; i < roughPathPointCount; ++i)
{
Vec3V pos = RCC_VEC3V(m_roughPathPolyLine[i].m_first);
pos += drawVertOffsetV;
grcDebugDraw::Cross(pos, 0.25f, roughPathPointsColour);
}
}
void RacingLine::SmoothedPathDraw(float drawVertOffset, Color32 smoothPathColour) const
{
// Draw the smoothed path.
const int smoothedPathPointCount = m_smoothedPathPolyLine.size();
for(int i = 0; i < (smoothedPathPointCount - 1); ++i)
{
Vector3 a = m_smoothedPathPolyLine[i].m_first;
a.z += drawVertOffset;
Vector3 b = m_smoothedPathPolyLine[i + 1].m_first;
b.z += drawVertOffset;
grcDebugDraw::Line(a, b, smoothPathColour);
}
}
// Draw the racing line.
void RacingLine::SmoothedPathDrawHeat( float drawVertOffset,
float vehVelMax,
float vehSideAccMax,
float UNUSED_PARAM(vehStartAccMax),
float vehStopAccMax
) const
{
const int smoothedPathPointCount = m_smoothedPathPolyLine.size();
if(smoothedPathPointCount == 0)
{
return;
}
Vec3V vDrawVertOffsetZ = Vec3VFromF32(drawVertOffset);
vDrawVertOffsetZ = And(vDrawVertOffsetZ, Vec3V(V_MASKZ));
TUNE_GROUP_BOOL(ACK, propagateMaxVels, true);
if(propagateMaxVels)
{
atArray<float> maxVelUnclampedAtNodes;
atArray<float> distToNextNodes;
for(int i = 0; i < smoothedPathPointCount; ++i)
{
float nodeBAngle = 0.0f;
float nodeBRadius = 0.0f;
SmoothedPathGetPointAngleAndRadiusOfCurvature(i, nodeBAngle, nodeBRadius);
const float nodeBVel2MaxUnclamped = nodeBRadius * vehSideAccMax;
Assert(nodeBVel2MaxUnclamped >= 0.0f);
const float nodeBVelMaxUnclamped = rage::Sqrtf(nodeBVel2MaxUnclamped);
maxVelUnclampedAtNodes.PushAndGrow(nodeBVelMaxUnclamped);
float distToNextNode = 0.0f;
if(i < (smoothedPathPointCount - 1))
{
distToNextNode = (m_smoothedPathPolyLine[i + 1].m_first - m_smoothedPathPolyLine[i].m_first).Mag();
}
else
{
// Just use the same value as the last link.
distToNextNode = (m_smoothedPathPolyLine[smoothedPathPointCount - 1].m_first - m_smoothedPathPolyLine[smoothedPathPointCount - 2].m_first).Mag();
}
distToNextNodes.PushAndGrow(distToNextNode);
}
// Propagate the max decelerations backwards (this assures we are never going too fast to make the upcoming turns).
for(int i = (smoothedPathPointCount - 1); i > 0; --i)
{
// The total braking acceleration over the available distance between the nodes must
// be taken into account.
const float currentNodesMaxVel = maxVelUnclampedAtNodes[i];
const float prevNodesMaxVel = maxVelUnclampedAtNodes[i - 1];
const float distBetweenNodes = distToNextNodes[i - 1];
const float prevNodesNewMaxVel2ToMakeNextNode = (currentNodesMaxVel * currentNodesMaxVel) + (2.0f * vehStopAccMax * distBetweenNodes);
Assert(prevNodesNewMaxVel2ToMakeNextNode >= 0.0f);
const float prevNodesNewMaxVelToMakeNextNode = rage::Sqrtf(prevNodesNewMaxVel2ToMakeNextNode);
if(prevNodesNewMaxVelToMakeNextNode < prevNodesMaxVel)
{
maxVelUnclampedAtNodes[i - 1] = prevNodesNewMaxVelToMakeNextNode;
}
}
// // Propagate the max accelerations forwards (this assures we are always going as fast as we can).
// for(int i = 0; i < (smoothedPathPointCount - 1); ++i)
// {
// // The total start acceleration over the available distance between the nodes must
// // be taken into account.
// const float currentNodesMaxVel = maxVelUnclampedAtNodes[i];
// const float nextNodesMaxVel = maxVelUnclampedAtNodes[i + 1];
// const float distBetweenNodes = distToNextNodes[i];
// const float nextNodesNewMaxVel2 = (currentNodesMaxVel * currentNodesMaxVel) + (2.0f * vehStartAccMax * distBetweenNodes);
// Assert(nextNodesNewMaxVel2 >= 0.0f);
// const float nextNodesNewMaxVel = rage::Sqrtf(nextNodesNewMaxVel2);
// if(nextNodesNewMaxVel < nextNodesMaxVel)
// {
// maxVelUnclampedAtNodes[i + 1] = nextNodesNewMaxVel;
// }
// }
Vec3V nodeAPos(RCC_VEC3V(m_smoothedPathPolyLine[0].m_first));
nodeAPos += vDrawVertOffsetZ;
Color32 nodeACurvatureBasedColour(0.0f, 1.0f, 0.0f, 1.0f);
for(int i = 0; i < smoothedPathPointCount; ++i)
{
Vec3V nodeBPos = VECTOR3_TO_VEC3V(SmoothedPathGetPointPos(i));
nodeBPos += vDrawVertOffsetZ;
float nodeBVelMaxUnclamped = maxVelUnclampedAtNodes[i];
float nodeBVelMaxClamped = rage::Clamp(nodeBVelMaxUnclamped, 0.0f, vehVelMax);
float nodeBVelMaxPortion = (nodeBVelMaxClamped / vehVelMax);
// Calculate the color to use.
const float nodeBCurvePortion = 1.0f - nodeBVelMaxPortion;
const float nodeBRedPortion = rage::Clamp(2.0f * nodeBCurvePortion, 0.0f, 1.0f);
const float nodeBGreenPortion = rage::Clamp(2.0f - (2.0f * nodeBCurvePortion), 0.0f, 1.0f);
Color32 nodeBCurvatureBasedColour(nodeBRedPortion, nodeBGreenPortion, 0.0f, 1.0f);
TUNE_BOOL(FORCE_RED, false);
TUNE_FLOAT(radius, 0.1f, 0.0f, 1.0f, 0.01f);
if(FORCE_RED){nodeBCurvatureBasedColour = Color32(1.0f, 0.0f, 0.0f, 1.0f);}
grcDebugDraw::Cylinder(nodeAPos, nodeBPos, radius, nodeACurvatureBasedColour, nodeBCurvatureBasedColour, false, true, 5);
// Update our rolling history.
nodeAPos = nodeBPos;
nodeACurvatureBasedColour = nodeBCurvatureBasedColour;
}
}
else
{
Vec3V nodeAPos(RCC_VEC3V(m_smoothedPathPolyLine[0].m_first));
nodeAPos += vDrawVertOffsetZ;
Color32 nodeACurvatureBasedColour(0.0f, 1.0f, 0.0f, 1.0f);
for(int i = 0; i < smoothedPathPointCount; ++i)
{
Vec3V nodeBPos = VECTOR3_TO_VEC3V(SmoothedPathGetPointPos(i));
nodeBPos += vDrawVertOffsetZ;
float nodeBAngle = 0.0f;
float nodeBRadius = 0.0f;
SmoothedPathGetPointAngleAndRadiusOfCurvature(i, nodeBAngle, nodeBRadius);
const float nodeBVel2MaxUnclamped = (nodeBRadius * vehSideAccMax);
Assert(nodeBVel2MaxUnclamped >= 0.0f);
const float nodeBVelMaxUnclamped = rage::Sqrtf(nodeBVel2MaxUnclamped);
const float nodeBVelMaxClamped = rage::Clamp(nodeBVelMaxUnclamped, 0.0f, vehVelMax);
const float nodeBVelMaxPortion = (rage::Abs(nodeBAngle) < 0.001f)?1.0f:(nodeBVelMaxClamped / vehVelMax);
// Calculate the color to use.
const float nodeBCurvePortion = 1.0f - nodeBVelMaxPortion;
const float nodeBRedPortion = rage::Clamp(2.0f * nodeBCurvePortion, 0.0f, 1.0f);
const float nodeBGreenPortion = rage::Clamp(2.0f - (2.0f * nodeBCurvePortion), 0.0f, 1.0f);
Color32 nodeBCurvatureBasedColour(nodeBRedPortion, nodeBGreenPortion, 0.0f, 1.0f);
TUNE_BOOL(FORCE_RED, false);
TUNE_FLOAT(radius, 0.1f, 0.0f, 1.0f, 0.01f);
if(FORCE_RED){nodeBCurvatureBasedColour = Color32(1.0f, 0.0f, 0.0f, 1.0f);}
grcDebugDraw::Cylinder(nodeAPos, nodeBPos, radius, nodeACurvatureBasedColour, nodeBCurvatureBasedColour, false, true, 5);
// Update our rolling history.
nodeAPos = nodeBPos;
nodeACurvatureBasedColour = nodeBCurvatureBasedColour;
}
}
}
#endif // __DEV
Vector3 RacingLine::GetClosestPointOnSegmentFlat(const Vector3& segmentAPos, const Vector3& segmentBPos, const Vector3& pos, float& segmentPortion) const
{
//const Vector3 pathPointABDelta = segmentBPos - segmentAPos;
Vector3 pathPointABDeltaFlat = segmentBPos - segmentAPos; pathPointABDeltaFlat.z = 0.0f;
Vector3 diffVehPosFlat(pos - segmentAPos); diffVehPosFlat.z = 0.0f;
float dotPrFlat = diffVehPosFlat.Dot(pathPointABDeltaFlat);
Vector3 closestPointOnSegmentFlat = segmentAPos;
segmentPortion = 0.0f;
if(dotPrFlat > 0.0f)
{
const float length2 = pathPointABDeltaFlat.Mag2();
dotPrFlat /= length2;
if (dotPrFlat >= 1.0f)
{
closestPointOnSegmentFlat = segmentBPos;
segmentPortion = 1.0f;
}
else
{
closestPointOnSegmentFlat = segmentAPos + pathPointABDeltaFlat * dotPrFlat;
segmentPortion = dotPrFlat;
}
}
closestPointOnSegmentFlat.z = 0.0f;
return closestPointOnSegmentFlat;
}
//---------------------------------------------------------------------------
// RacingLineCubic
//---------------------------------------------------------------------------
RacingLineCubic::RacingLineCubic()
{
;
}
RacingLineCubic::~RacingLineCubic()
{
;
}
bool RacingLineCubic::SmoothedPathInit(int numSmoothPathSegsPerRoughSeg)
{
RacingLine::SmoothedPathInit(numSmoothPathSegsPerRoughSeg);
if(m_roughPathPolyLine.size() >= 4)
{
MakeSmoothedPathCubic(m_numSmoothPathSegsPerRoughSeg);
return true;
}
return false;
}
void RacingLineCubic::SmoothedPathAdvance(int numSmoothPathSegsPerRoughSeg)
{
// Possible re-init the smoothed path.
RacingLine::SmoothedPathAdvance(numSmoothPathSegsPerRoughSeg);
// Just remake the entire smooth path as the changes can propagate
// to several nodes away.
SmoothedPathInit(m_numSmoothPathSegsPerRoughSeg);
}
// Only call if there are 4 rough path points points(m_roughPathPointCount > 3)...
void RacingLineCubic::MakeSmoothedPathCubic(int numSmoothPathSegsPerRoughSeg)
{
const int roughPathPointCount = m_roughPathPolyLine.size();
Assert(roughPathPointCount > 3);
// Make the points.
for(int i = 0; i < roughPathPointCount; ++i)
{
Vector3 roughPathPointPrev;
if(i > 0)
{
roughPathPointPrev = m_roughPathPolyLine[i - 1].m_first;
}
else
{
roughPathPointPrev = (m_roughPathPolyLine[0].m_first * 2.0f) - m_roughPathPolyLine[1].m_first;
}
Vector3 roughPathPointCurr = m_roughPathPolyLine[i].m_first;
Vector3 roughPathPointNext;
if(i < (roughPathPointCount - 1))
{
roughPathPointNext = m_roughPathPolyLine[i + 1].m_first;
}
else // i == (roughPathPointCount - 1)
{
roughPathPointNext = (m_roughPathPolyLine[roughPathPointCount - 1].m_first * 2.0f) - m_roughPathPolyLine[roughPathPointCount - 2].m_first;
}
Vector3 roughPathPointFutr;
if(i < (roughPathPointCount - 2))
{
roughPathPointFutr = m_roughPathPolyLine[i + 2].m_first;
}
else if(i == (roughPathPointCount - 2))
{
roughPathPointFutr = (m_roughPathPolyLine[roughPathPointCount - 1].m_first * 2.0f) - m_roughPathPolyLine[roughPathPointCount - 2].m_first;
}
else // i == (roughPathPointCount - 1)
{
roughPathPointFutr = (m_roughPathPolyLine[roughPathPointCount - 1].m_first * 3.0f) - (m_roughPathPolyLine[roughPathPointCount - 2].m_first * 2.0f);
}
float a[4];
a[0] =(-roughPathPointPrev.x + 3.0f * roughPathPointCurr.x - 3.0f * roughPathPointNext.x + roughPathPointFutr.x) / 6.0f;
a[1] =(3.0f * roughPathPointPrev.x - 6.0f * roughPathPointCurr.x + 3.0f * roughPathPointNext.x) / 6.0f;
a[2] =(-3.0f * roughPathPointPrev.x + 3.0f * roughPathPointNext.x) / 6.0f;
a[3] =(roughPathPointPrev.x + 4.0f * roughPathPointCurr.x + roughPathPointNext.x) / 6.0f;
float b[4];
b[0] =(-roughPathPointPrev.y + 3.0f * roughPathPointCurr.y - 3.0f * roughPathPointNext.y + roughPathPointFutr.y) / 6.0f;
b[1] =(3.0f * roughPathPointPrev.y - 6.0f * roughPathPointCurr.y + 3.0f * roughPathPointNext.y) / 6.0f;
b[2] =(-3.0f * roughPathPointPrev.y + 3.0f * roughPathPointNext.y) / 6.0f;
b[3] =(roughPathPointPrev.y + 4.0f * roughPathPointCurr.y + roughPathPointNext.y) / 6.0f;
float c[4];
c[0] =(-roughPathPointPrev.z + 3.0f * roughPathPointCurr.z - 3.0f * roughPathPointNext.z + roughPathPointFutr.z) / 6.0f;
c[1] =(3.0f * roughPathPointPrev.z - 6.0f * roughPathPointCurr.z + 3.0f * roughPathPointNext.z) / 6.0f;
c[2] =(-3.0f * roughPathPointPrev.z + 3.0f * roughPathPointNext.z) / 6.0f;
c[3] =(roughPathPointPrev.z + 4.0f * roughPathPointCurr.z + roughPathPointNext.z) / 6.0f;
// Determine if we should add the last point or not.
const bool addLastPointAlongSegment = (i == (roughPathPointCount - 1));
// Add the smooth points.
const int numPoints = addLastPointAlongSegment?numSmoothPathSegsPerRoughSeg:numSmoothPathSegsPerRoughSeg - 1;
for(int j = 0; j < numPoints; ++j)
{
const float t = static_cast<float>(j) / static_cast<float>(numSmoothPathSegsPerRoughSeg - 1);
const float x = a[3] + t *(a[2] + t *(a[1] + t * a[0]));
const float y = b[3] + t *(b[2] + t *(b[1] + t * b[0]));
const float z = c[3] + t *(c[2] + t *(c[1] + t * c[0]));
const Vector3 newPoint(x, y, z);
#if __ASSERT
const int smoothedPathPointCount = m_smoothedPathPolyLine.size();
if(smoothedPathPointCount > 0)
{
const Vector3 lastAddedPoint = m_smoothedPathPolyLine[smoothedPathPointCount - 1].m_first;
Assert(newPoint != lastAddedPoint);
}
#endif // __ASSERT
PointWithUpdateStamp pointWithUpdateStamp(newPoint, m_roughPathPolyLine[i].m_second);
//m_smoothedPathPolyLine.push_back(pointWithUpdateStamp);
m_smoothedPathPolyLine.PushAndGrow(pointWithUpdateStamp);
}
}
}
//---------------------------------------------------------------------------
// RacingLineCardinal
//---------------------------------------------------------------------------
RacingLineCardinal::RacingLineCardinal()
{
;
}
RacingLineCardinal::~RacingLineCardinal()
{
;
}
//returns whether we were able to smooth the path
bool RacingLineCardinal::SmoothedPathInit(int numSmoothPathSegsPerRoughSeg)
{
RacingLine::SmoothedPathInit(numSmoothPathSegsPerRoughSeg);
if(m_roughPathPolyLine.size() >= 4)
{
MakeSmoothedPathCardinal(m_numSmoothPathSegsPerRoughSeg);
return true;
}
return false;
}
void RacingLineCardinal::SmoothedPathAdvance(int numSmoothPathSegsPerRoughSeg)
{
// Possible re-init the smoothed path.
RacingLine::SmoothedPathAdvance(numSmoothPathSegsPerRoughSeg);
// Just remake the entire smooth path as the changes can propagate
// to several nodes away.
SmoothedPathInit(m_numSmoothPathSegsPerRoughSeg);
}
// CardinalPath is responsible for connecting
// a set of points with curves.
void RacingLineCardinal::MakeSmoothedPathCardinal(int numSmoothPathSegsPerRoughSeg)
{
const int roughPathPointCount = m_roughPathPolyLine.size();
Assert(roughPathPointCount > 3);
// Set up the path output.
for(int i = 0; i < roughPathPointCount; ++i)
{
float t = 0.0f;
float deltaTPerPathStep = 1.0f / static_cast<float>(numSmoothPathSegsPerRoughSeg - 1);
// Determine if we should add the last point or not.
const bool addLastPointAlongSegment = (i == (roughPathPointCount - 1));
// Add the smooth points.
const int numPoints = addLastPointAlongSegment?numSmoothPathSegsPerRoughSeg:numSmoothPathSegsPerRoughSeg - 1;
for(int j = 0; j < numPoints; ++j)
{
Vector3 roughPathPointPrev;
if(i > 0)
{
roughPathPointPrev = m_roughPathPolyLine[i - 1].m_first;
}
else
{
roughPathPointPrev = (m_roughPathPolyLine[0].m_first * 2.0f) - m_roughPathPolyLine[1].m_first;
}
Vector3 roughPathPointCurr = m_roughPathPolyLine[i].m_first;
Vector3 roughPathPointNext;
if(i < (roughPathPointCount - 1))
{
roughPathPointNext = m_roughPathPolyLine[i + 1].m_first;
}
else // i == (roughPathPointCount - 1)
{
roughPathPointNext = (m_roughPathPolyLine[roughPathPointCount - 1].m_first * 2.0f) - m_roughPathPolyLine[roughPathPointCount - 2].m_first;
}
Vector3 roughPathPointFutr;
if(i < (roughPathPointCount - 2))
{
roughPathPointFutr = m_roughPathPolyLine[i + 2].m_first;
}
else if(i == (roughPathPointCount - 2))
{
roughPathPointFutr = (m_roughPathPolyLine[roughPathPointCount - 1].m_first * 2.0f) - m_roughPathPolyLine[roughPathPointCount - 2].m_first;
}
else // i == (roughPathPointCount - 1)
{
roughPathPointFutr = (m_roughPathPolyLine[roughPathPointCount - 1].m_first * 3.0f) - (m_roughPathPolyLine[roughPathPointCount - 2].m_first * 2.0f);
}
const float t1 = 1.0f - t;
const float t12 = t1 * t1;
const float t2 = t * t;
const float cp0 = t1 * t12;
const float cp1 = 3.0f * t* t12;
const float cp2 = 3.0f * t2* t1;
const float cp3 = t * t2;
t += deltaTPerPathStep;
const float x = (roughPathPointCurr.x * cp0) +
(roughPathPointCurr.x +(roughPathPointNext.x - roughPathPointPrev.x) / 6.0f) * cp1 +
(roughPathPointNext.x -(roughPathPointFutr.x - roughPathPointCurr.x) / 6.0f) * cp2 +
(roughPathPointNext.x * cp3);
const float y = (roughPathPointCurr.y * cp0) +
(roughPathPointCurr.y +( roughPathPointNext.y - roughPathPointPrev.y) / 6.0f) * cp1 +
(roughPathPointNext.y -(roughPathPointFutr.y - roughPathPointCurr.y) / 6.0f) * cp2 +
(roughPathPointNext.y * cp3);
const float z = (roughPathPointCurr.z * cp0) +
(roughPathPointCurr.z +( roughPathPointNext.z - roughPathPointPrev.z) / 6.0f) * cp1 +
(roughPathPointNext.z -(roughPathPointFutr.z - roughPathPointCurr.z) / 6.0f) * cp2 +
(roughPathPointNext.z * cp3);
const Vector3 newPoint(x, y, z);
#if __ASSERT
const int smoothedPathPointCount = m_smoothedPathPolyLine.size();
if(smoothedPathPointCount > 0)
{
const Vector3 lastAddedPoint = m_smoothedPathPolyLine[smoothedPathPointCount - 1].m_first;
Assert(newPoint != lastAddedPoint);
}
#endif // __ASSERT
PointWithUpdateStamp pointWithUpdateStamp(newPoint, m_roughPathPolyLine[i].m_second);
//m_smoothedPathPolyLine.push_back(pointWithUpdateStamp);
m_smoothedPathPolyLine.PushAndGrow(pointWithUpdateStamp);
}
}
}
//---------------------------------------------------------------------------
// CRacingLineMass
// The racing line is approximated by a series of points (masses) connected
// by vectors (springs). This is the representation of one of the masses.
//---------------------------------------------------------------------------
CRacingLineMass::CRacingLineMass()
:
m_updateStamp (0),
m_mass (0.0f),
m_pos (0.0f, 0.0f, 0.0f),
m_vel (0.0f, 0.0f),
m_force (0.0f, 0.0f)
{
;
}
void CRacingLineMass::Set( const u32 updateStamp,
const float mass,
const Vector3& pos)
{
Assert(mass > 0.0f);
m_updateStamp = updateStamp;
m_mass = mass;
m_pos = pos;
m_vel.Set(0.0f, 0.0f);
m_force.Set(0.0f, 0.0f);
}
//---------------------------------------------------------------------------
// CRacingLineSpring
// The racing line is approximated by a series of points (masses) connected
// by vectors (springs). This is the representation of one of the springs.
//---------------------------------------------------------------------------
CRacingLineSpring::CRacingLineSpring()
:
m_updateStamp (0),
m_massIndex0 (-1),
m_massIndex1 (-1),
m_restLength (0.0f),
m_stiffness (0.0f)
{
;
}
void CRacingLineSpring::Set( const u32 updateStamp,
const float restLength,
const float stiffness,
const int massIndex0,
const int massIndex1)
{
Assert(restLength > 0.0f);
Assert(stiffness > 0.0f);
Assert(massIndex0 >= 0);
Assert(massIndex1 >= 0);
m_updateStamp = updateStamp;
m_massIndex0 = massIndex0;
m_massIndex1 = massIndex1;
m_restLength = restLength;
m_stiffness = stiffness;
}
//---------------------------------------------------------------------------
// RacingLineOptimizing
//---------------------------------------------------------------------------
RacingLineOptimizing::RacingLineOptimizing()
: // Initializer list.
m_numRaceLineMassesPerRoughSeg (0)
{
;
}
RacingLineOptimizing::~RacingLineOptimizing()
{
;
}
void RacingLineOptimizing::ClearAll(void)
{
RacingLine::ClearAll();
m_masses.clear();
m_springs.clear();
m_boundPolyLines.clear();
}
void RacingLineOptimizing::ClearStampedData(u32 updateStamp)
{
RacingLine::ClearStampedData(updateStamp);
while(m_masses.size() && m_masses[0].m_updateStamp == updateStamp)
{
m_masses.pop_front();
}
while(m_springs.size() && m_springs[0].m_updateStamp == updateStamp)
{
m_springs.pop_front();
}
const int boundPolyLineCount = m_boundPolyLines.GetCount();
for(int i = 0; i < boundPolyLineCount; ++i)
{
while(m_boundPolyLines[i].m_first.size() && m_boundPolyLines[i].m_first[0].m_second == updateStamp)
{
m_boundPolyLines[i].m_first.pop_front();
}
}
}
void RacingLineOptimizing::BoundPolyLinesAddPoint(u32 boundPolyLineId, u32 updateStamp, const Vector3& boundPolyLineNewPoint)
{
bool boundPolyLineIdFound = false;
const int boundPolyLineCount = m_boundPolyLines.GetCount();
for(int i = 0; i < boundPolyLineCount; ++i)
{
if(m_boundPolyLines[i].m_second == boundPolyLineId)
{
boundPolyLineIdFound = true;
PointWithUpdateStamp pointWithUpdateStamp(boundPolyLineNewPoint, updateStamp);
m_boundPolyLines[i].m_first.push_back(pointWithUpdateStamp);
}
}
if(!boundPolyLineIdFound)
{
BoundPolyLineWithId temp;
temp.m_second = boundPolyLineId;
m_boundPolyLines.PushAndGrow(temp);
PointWithUpdateStamp pointWithUpdateStamp(boundPolyLineNewPoint, updateStamp);
m_boundPolyLines[m_boundPolyLines.GetCount() - 1].m_first.push_back(pointWithUpdateStamp);
}
}
bool RacingLineOptimizing::SmoothedPathInit(int numSmoothPathSegsPerRoughSeg)
{
RacingLine::SmoothedPathInit(numSmoothPathSegsPerRoughSeg);
RaceLineInit(m_numSmoothPathSegsPerRoughSeg);
TUNE_GROUP_INT(PATH_RACING_LINE, relaxIterationsToDoOnInit, 300, 1, 1000, 1);
for(int i = 0; i < relaxIterationsToDoOnInit; ++i)
{
RaceLineUpdate();
}
SmoothedPathMakeFromRacingLine();
return true;
}
void RacingLineOptimizing::SmoothedPathAdvance(int numSmoothPathSegsPerRoughSeg)
{
// Possible re-init the smoothed path.
RacingLine::SmoothedPathAdvance(numSmoothPathSegsPerRoughSeg);
// Just remake the entire smooth path as the changes can propagate
// to several nodes away.
RaceLineAdvance(m_numSmoothPathSegsPerRoughSeg);
TUNE_GROUP_INT(PATH_RACING_LINE, relaxIterationsToDoOnAdvance, 20, 1, 1000, 1);
for(int i = 0; i < relaxIterationsToDoOnAdvance; ++i)
{
RaceLineUpdate();
}
SmoothedPathMakeFromRacingLine();
}
void RacingLineOptimizing::SmoothedPathUpdate(void)
{
TUNE_GROUP_INT(PATH_RACING_LINE, relaxIterationsToDoOnUpdate, 10, 1, 1000, 1);
for(int i = 0; i < relaxIterationsToDoOnUpdate; ++i)
{
RaceLineUpdate();
}
SmoothedPathMakeFromRacingLine();
}
void RacingLineOptimizing::Draw( float DEV_ONLY(drawVertOffset),
Color32 DEV_ONLY(roughPathPointsColour),
Color32 DEV_ONLY(smoothPathColour),
bool DEV_ONLY(drawPathAsHeath),
float DEV_ONLY(vehVelMax),
float DEV_ONLY(vehSideAccMax),
float DEV_ONLY(vehStartAccMax),
float DEV_ONLY(vehStopAccMax)
) const
{
#if __DEV
RoughPathPointsDraw(drawVertOffset, roughPathPointsColour);
if(drawPathAsHeath)
{
SmoothedPathDrawHeat(drawVertOffset, vehVelMax, vehSideAccMax, vehStartAccMax, vehStopAccMax);
}
else
{
SmoothedPathDraw(drawVertOffset, smoothPathColour);
}
// Draw the track
TUNE_GROUP_BOOL(PATH_RACING_TRACK, boundsDraw, true);
TUNE_GROUP_COLOR(PATH_RACING_TRACK, boundsColour, Color32(0.0f, 0.0f, 1.0f, 1.0f));
if(boundsDraw)
{
const int boundPolyLineCount = m_boundPolyLines.GetCount();
for(int i = 0; i < boundPolyLineCount; ++i)
{
for(int j = 0; j < m_boundPolyLines[i].m_first.size() - 1; ++j)
{
Vector3 boundSegA(m_boundPolyLines[i].m_first[j].m_first);
Vector3 boundSegB(m_boundPolyLines[i].m_first[j + 1].m_first);
Vector3 boundSegAHigh(boundSegA.x, boundSegA.y, boundSegA.z + (2.0f * drawVertOffset));
Vector3 boundSegBHigh(boundSegB.x, boundSegB.y, boundSegB.z + (2.0f * drawVertOffset));
grcDebugDraw::Poly(RCC_VEC3V(boundSegA), RCC_VEC3V(boundSegB), RCC_VEC3V(boundSegAHigh), boundsColour, true);
grcDebugDraw::Poly(RCC_VEC3V(boundSegAHigh), RCC_VEC3V(boundSegB), RCC_VEC3V(boundSegBHigh), boundsColour, true);
}
}
}
#endif // __DEV
}
void RacingLineOptimizing::SmoothedPathMakeFromRacingLine(void)
{
m_smoothedPathPolyLine.clear();
const int massCount = m_masses.size();
for(int i = 0; i < massCount; ++i)
{
PointWithUpdateStamp pointWithUpdateStamp(m_masses[i].m_pos, m_masses[i].m_updateStamp);
//m_smoothedPathPolyLine.push_back(pointWithUpdateStamp);
m_smoothedPathPolyLine.PushAndGrow(pointWithUpdateStamp);
}
}
void RacingLineOptimizing::RaceLineReset(void)
{
m_numRaceLineMassesPerRoughSeg = 0;
m_masses.clear();
m_springs.clear();
}
// This function uses the existing track to set the initial approximation
// to its racing line to lie along the track's centre line.
void RacingLineOptimizing::RaceLineInit(int numRaceLineMassesPerRoughSeg)
{
Assert(numRaceLineMassesPerRoughSeg > 0);
const int roughPathNodeCount = m_roughPathPolyLine.size();
Assert(roughPathNodeCount > 1);
RaceLineReset();
m_numRaceLineMassesPerRoughSeg = numRaceLineMassesPerRoughSeg;
// Make the racing line initially follow the center of the track.
// For looped tracks, create an extra spring at the end connecting the
// first and last masses (though we don't do this right now).
TUNE_GROUP_FLOAT(PATH_RACING_LINE, springLengthScaleInit, 0.9f, 0.0f, 1.0f, 0.001f);
TUNE_GROUP_FLOAT(PATH_RACING_LINE, springStiffnessInit, 1.0f, 0.0f, 10.0f, 0.01f);
TUNE_GROUP_FLOAT(PATH_RACING_LINE, nodeMassInit, 1.0f, 0.0f, 10.0f, 0.01f);
const int massCount = roughPathNodeCount + ((roughPathNodeCount - 1) * (m_numRaceLineMassesPerRoughSeg - 1));
for(int massIndex = 0; massIndex < massCount; ++massIndex)
{
const u32 updateStamp = m_roughPathPolyLine[massIndex / m_numRaceLineMassesPerRoughSeg].m_second;
// Add a new point to the racing line which lies at the
// centre of the track at the start of the new segment.
// Connect the new point to the last one that was created
// using a spring so that the distance between the points
// remains roughly fixed as the racing line is optimized.
Vector3 mass1InitialPos = RaceLineMassGetInitialPosFromRoughPath(massIndex);
CRacingLineMass tempMass;
tempMass.Set(updateStamp, nodeMassInit, mass1InitialPos);
m_masses.push_back(tempMass);
if(massIndex > 0)
{
Vector3 mass0InitialPos = RaceLineMassGetInitialPosFromRoughPath(massIndex - 1);
const Vector2 mass0InitialPos2(mass0InitialPos, Vector2::kXY);
const Vector2 mass1InitialPos2(mass1InitialPos, Vector2::kXY);
const float initialLength = (mass1InitialPos2 - mass0InitialPos2).Mag();
Assert(initialLength > 0.0f);
const float naturalLengthOfSpring = initialLength * springLengthScaleInit;
CRacingLineSpring tempSpring;
tempSpring.Set(updateStamp, naturalLengthOfSpring, springStiffnessInit, (massIndex - 1), massIndex);
m_springs.push_back(tempSpring);
}
}
}
void RacingLineOptimizing::RaceLineAdvance(int numRaceLineMassesPerRoughSeg)
{
Assert(numRaceLineMassesPerRoughSeg > 0);
const int roughPathNodeCount = m_roughPathPolyLine.size();
Assert(roughPathNodeCount > 1);
// Check if we need to re-init the race line.
if(numRaceLineMassesPerRoughSeg != m_numRaceLineMassesPerRoughSeg)
{
RaceLineInit(numRaceLineMassesPerRoughSeg);
return;
}
// Make the racing line initially follow the center of the track.
// For looped tracks, create an extra spring at the end connecting the
// first and last masses (though we don't do this right now).
TUNE_GROUP_FLOAT(PATH_RACING_LINE, springLengthScaleAdvance, 0.9f, 0.0f, 1.0f, 0.001f);
TUNE_GROUP_FLOAT(PATH_RACING_LINE, springStiffnessAdvance, 1.0f, 0.0f, 10.0f, 0.01f);
TUNE_GROUP_FLOAT(PATH_RACING_LINE, nodeMassAdvance, 1.0f, 0.0f, 10.0f, 0.01f);
const u32 updateStamp = m_roughPathPolyLine[roughPathNodeCount - 1].m_second;
for(int i = 0; i < m_numRaceLineMassesPerRoughSeg; ++i)
{
const int massIndex = (((roughPathNodeCount - 2) * m_numRaceLineMassesPerRoughSeg) + i) + 1;
// Add a new point to the racing line which lies at the
// centre of the track at the start of the new segment.
// Connect the new point to the last one that was created
// using a spring so that the distance between the points
// remains roughly fixed as the racing line is optimized.
Vector3 mass1InitialPos = RaceLineMassGetInitialPosFromRoughPath(massIndex);
CRacingLineMass tempMass;
tempMass.Set(updateStamp, nodeMassAdvance, mass1InitialPos);
m_masses.push_back(tempMass);
if(massIndex > 0)
{
Vector3 mass0InitialPos = RaceLineMassGetInitialPosFromRoughPath(massIndex - 1);
const Vector2 mass0InitialPos2(mass0InitialPos, Vector2::kXY);
const Vector2 mass1InitialPos2(mass1InitialPos, Vector2::kXY);
const float initialLength = (mass1InitialPos2 - mass0InitialPos2).Mag();
Assert(initialLength > 0.0f);
const float naturalLengthOfSpring = initialLength * springLengthScaleAdvance;
CRacingLineSpring tempSpring;
tempSpring.Set(updateStamp, naturalLengthOfSpring, springStiffnessAdvance, (massIndex - 1), massIndex);
m_springs.push_back(tempSpring);
}
}
}
// Since we can have multiple masses per track element this function
// makes getting the center position easier.
Vector3 RacingLineOptimizing::RaceLineMassGetInitialPosFromRoughPath(int massIndex) const
{
Assert(massIndex >= 0);
Assert(m_roughPathPolyLine.size() > 1);
const int roughPathNodeIndexA = (massIndex / m_numRaceLineMassesPerRoughSeg);
Assert(roughPathNodeIndexA < m_roughPathPolyLine.size());
// Check if we are on top of a rough path node or need to use an
// interpolated position.
const bool onRoughPathNode = ((massIndex % m_numRaceLineMassesPerRoughSeg) == 0);
if(onRoughPathNode)
{
const Vector3 pos(m_roughPathPolyLine[roughPathNodeIndexA].m_first);
Assert(Vec3IsValid(pos));
return pos;
}
else
{
const int roughPathNodeIndexB = roughPathNodeIndexA + 1;
const float lerpFactorAB = static_cast<float>(massIndex % m_numRaceLineMassesPerRoughSeg) / static_cast<float>(m_numRaceLineMassesPerRoughSeg);
const Vector3 roughPathNodePosA = m_roughPathPolyLine[roughPathNodeIndexA].m_first;
const Vector3 roughPathNodePosB = m_roughPathPolyLine[roughPathNodeIndexB].m_first;
const Vector3 roughPathNodeDeltaAB = roughPathNodePosB - roughPathNodePosA;
Assert(roughPathNodeDeltaAB.Mag() > 0.0f);
const Vector3 pos(roughPathNodePosA + (roughPathNodeDeltaAB * lerpFactorAB));
Assert(Vec3IsValid(pos));
return pos;
}
}
// This is the main function that causes the racing line to smooth out. It
// should be called repeatedly with the game update loop.
// In an actual game it would do the smoothing all in one function call when
// the path is set up.
void RacingLineOptimizing::RaceLineUpdate(void)
{
// Update the positions of the points in the racing line
// approximation.
// This function computes the forces that need to be applied to
// the points in the racing line in order to improve the
// approximation.
UpdateForces();
// Update the velocities of the masses in the racing line given
// the forces applied to them.
UpdateVelocities();
// Update the positions of the masses in the racing line given
// their velocities.
UpdatePositions();
}
// Calculate all the forces that will operate on the racing line masses.
void RacingLineOptimizing::UpdateForces(void)
{
// reset all forces to get ready to calculate new ones.
const int massCount = m_masses.size();
for(int i = 0; i < massCount; ++i)
{
m_masses[i].m_force.Set(0.0f, 0.0f);
}
// Calculate the restoring forces that the springs apply to the
// masses in the racing line. These forces help to keep the masses
// evenly distributed along the initialLength of the racing line.
TUNE_GROUP_BOOL(PATH_RACING_LINE, doLengthAdjustment, true);
if(doLengthAdjustment)
{
const int springCount = m_springs.size();
for(int i = 0; i < springCount; ++i)
{
CRacingLineSpring& spring = m_springs[i];
const int springMassIndex0 = spring.m_massIndex0;
#if __ASSERT
const int springMassIndex1 = spring.m_massIndex1;
#endif // __ASSERT
Assert((springMassIndex0 >= 0) && (springMassIndex0 < m_masses.size()));
Assert((springMassIndex1 >= 0) && (springMassIndex1 < m_masses.size()));
TDequeNode<CRacingLineMass>* pSpringMass0Node = m_masses.GetNode(springMassIndex0);
CRacingLineMass& springMass0 = pSpringMass0Node->m_data;//m_masses[springMassIndex0];
Assert(Vec3IsValid(springMass0.m_pos));
TDequeNode<CRacingLineMass>* pSpringMass1Node = pSpringMass0Node->m_pNext;
CRacingLineMass& springMass1 = pSpringMass1Node->m_data;//m_masses[springMassIndex1];
Assert(Vec3IsValid(springMass1.m_pos));
const Vector2 mass0InitialPos(springMass0.m_pos, Vector2::kXY);
const Vector2 mass1InitialPos(springMass1.m_pos, Vector2::kXY);
const Vector2 springDelta0to1 = mass1InitialPos - mass0InitialPos;
Assert(Vec2IsValid(springDelta0to1));
Vector2 springDir = springDelta0to1;
springDir.Normalize();
Assert(Vec2IsValid(springDir));
Assert(springDir.Mag() > 0.99f);
const float lengthRestoringForceScale = spring.m_stiffness * ((springDelta0to1.Mag() - spring.m_restLength) / spring.m_restLength);
const Vector2 lengthRestoringForce = springDir * lengthRestoringForceScale;
if(i != 0)
{
springMass0.m_force += lengthRestoringForce;
}
if(i != springCount - 1)
{
springMass1.m_force -= lengthRestoringForce;
}
Assert(Vec2IsValid(springMass0.m_force));
Assert(Vec2IsValid(springMass1.m_force));
}
}
}
// Calculate all the velocities that will operate on the racing line masses.
void RacingLineOptimizing::UpdateVelocities(void)
{
// Update the velocities of the masses in the racing line according
// to the forces applied to them.
// Scale the forces according to the largest of them to produce a
// good trade of force between speed and stability.
float largestForce = 0.0f;
const int massCount = m_masses.size();
for(int i = 0; i < massCount; ++i)
{
const float forceMag = m_masses[i].m_force.Mag();
if(forceMag > largestForce)
{
largestForce = forceMag;
}
}
// Update the velocities according to the scaled forces.
for(int i = 0; i < massCount; ++i)
{
const float forceScale = (0.1f * largestForce) / (1.0f + m_masses[i].m_mass);
const Vector2 scaledForce = (m_masses[i].m_force * forceScale);
m_masses[i].m_vel += scaledForce;
Assert(Vec2IsValid(m_masses[i].m_vel));
}
// Reduce the velocities a little. This helps to keep the racing line
// approximation stable.
TUNE_GROUP_FLOAT(PATH_RACING_LINE, nodeVelocityDamping, 0.99f, 0.0f, 1.0f, 0.001f);
for(int i = 0; i < massCount; ++i)
{
m_masses[i].m_vel *= nodeVelocityDamping;
Assert(Vec2IsValid(m_masses[i].m_vel));
}
}
// Calculate all the new positions for the racing line masses.
void RacingLineOptimizing::UpdatePositions(void)
{
// Update the positions of the masses that form the racing line
// according to their velocities, making sure that they stay
// within the bounds of the track.
// This loop does not process the position of the first or last point
// of the racing line and hence creates a racing line that always starts
// and ends at the centre of the track.
const int massCount = m_masses.size();
for(int i = 1; i < massCount - 1; ++i)
{
const Vector3 currMassOldPos = m_masses[i].m_pos;
const Vector3 currMassNewPos = currMassOldPos + Vector3(m_masses[i].m_vel.x, m_masses[i].m_vel.y, 0.0f);
Vector3 earliestCollisionPos(currMassOldPos);
const bool massCollided = CheckIfSegmentCollidesWithBounds(currMassOldPos, currMassNewPos, earliestCollisionPos);
const Vector3 prevMassNewPos = m_masses[i - 1].m_pos;
const bool segmentPrevCollided = CheckIfSegmentCollidesWithBounds(prevMassNewPos, currMassNewPos, earliestCollisionPos);
const Vector3 nextMassOldPos = m_masses[i + 1].m_pos;
const bool segmentNextCollided = CheckIfSegmentCollidesWithBounds(currMassNewPos, nextMassOldPos, earliestCollisionPos);
if(massCollided && !segmentPrevCollided && !segmentNextCollided)
{
m_masses[i].m_pos = earliestCollisionPos;
m_masses[i].m_vel.Zero();
}
else if(segmentPrevCollided || segmentNextCollided)
{
m_masses[i].m_vel.Zero();
m_masses[i].m_pos = currMassOldPos;
}
else
{
m_masses[i].m_pos = currMassNewPos;
}
Assert(Vec3IsValid(m_masses[i].m_pos));
}
}
bool RacingLineOptimizing::CheckIfSegmentCollidesWithBounds(const Vector3& oldPos, const Vector3& newPos, Vector3& collisionPosEarliest_out)
{
bool collidedAnySegment = false;
float collisionMovePortionEarliest = 0.0f;
Vector3 collisionPosEarliest(oldPos);
const int boundPolyLineCount = m_boundPolyLines.GetCount();
for(int i = 0; i < boundPolyLineCount; ++i)
{
for(int j = 0; j < m_boundPolyLines[i].m_first.size() - 1; ++j)
{
Vector3 boundSegA(m_boundPolyLines[i].m_first[j].m_first);
Vector3 boundSegB(m_boundPolyLines[i].m_first[j + 1].m_first);
float collisionMovePortion = 0.0f;
Vector3 collisionPos(oldPos);
const IntersectResult result = Test2DSegmentSegment(oldPos, newPos, boundSegA, boundSegB, collisionMovePortion, collisionPos);
if(result == COINCIDENT || result == INTERESECTING)
{
collidedAnySegment = true;
if(collisionMovePortion < collisionMovePortionEarliest)
{
collisionMovePortionEarliest = collisionMovePortion;
collisionPosEarliest = collisionPos;
}
}
}
}
if(collidedAnySegment)
{
collisionPosEarliest_out = collisionPosEarliest;
return true;
}
else
{
return false;
}
}
// Implementation of the theory provided by Paul Bourke.
RacingLineOptimizing::IntersectResult RacingLineOptimizing::Test2DSegmentSegment(const Vector3& seg0A, const Vector3& seg0B, const Vector3& seg1A, const Vector3& seg1B, float& seg0Portion_out, Vector3& collisionPos_out)
{
const float denominator = (((seg1B.y - seg1A.y)*(seg0B.x - seg0A.x)) - ((seg1B.x - seg1A.x)*(seg0B.y - seg0A.y)));
const float numeratorA = (((seg1B.x - seg1A.x)*(seg0A.y - seg1A.y)) - ((seg1B.y - seg1A.y)*(seg0A.x - seg1A.x)));
const float numeratorB = (((seg0B.x - seg0A.x)*(seg0A.y - seg1A.y)) - ((seg0B.y - seg0A.y)*(seg0A.x - seg1A.x)));
if(denominator == 0.0f)
{
if(numeratorA == 0.0f && numeratorB == 0.0f)
{
return RacingLineOptimizing::COINCIDENT;
}
return RacingLineOptimizing::PARALLEL;
}
const float seg0Portion = numeratorA / denominator;
const float seg1Portion = numeratorB / denominator;
if(seg0Portion >= 0.0f && seg0Portion <= 1.0f && seg1Portion >= 0.0f && seg1Portion <= 1.0f)
{
// Get the intersection point.
seg0Portion_out = seg0Portion;
collisionPos_out.x = seg0A.x + seg0Portion_out*(seg0B.x - seg0A.x);
collisionPos_out.y = seg0A.y + seg0Portion_out*(seg0B.y - seg0A.y);
collisionPos_out.y = seg0A.z + seg0Portion_out*(seg0B.z - seg0A.z);
return RacingLineOptimizing::INTERESECTING;
}
return RacingLineOptimizing::NOT_INTERESECTING;
}