The-Simpsons-Hit-and-Run/game/code/roads/roadsegment.cpp

792 lines
23 KiB
C++

/*===========================================================================
Copyright (C) 2000 Radical Entertainment Ltd. All rights reserved.
Component: RoadSegment
Description:
Authors: Travis Brown-John
Revisions Date Author Revision
2002/02/25 Tbrown-John Created
===========================================================================*/
#include <roads/road.h>
#include <roads/roadsegment.h>
#include <roads/roadsegmentdata.h>
RoadSegment::RoadSegment()
:
mRoad( NULL ),
mSegmentIndex( 0 )
{
}
RoadSegment::~RoadSegment( void )
{
}
/*
void RoadSegment::Init( RoadSegmentData* rsd, rmt::Matrix& hierarchy, float scaleAlongFacing )
{
////////////////////////////////////////////////////////////////
// Transform segment data based on given matrix & scale-along-facing
//
rmt::Vector vector;
// First, do the corners and the edgenormals
for( int i=0; i<4; i++ )
{
// transform the corner
vector = rsd->GetCorner( i );
vector.z *= scaleAlongFacing;
vector.Transform( hierarchy );
mCorners[ i ] = vector;
// transform the edge normals
vector = rsd->GetEdgeNormal( i );
vector.Rotate( hierarchy );
mEdgeNormals[ i ] = vector;
}
// Now, transform the segment normal
vector = rsd->GetSegmentNormal();
vector.Rotate( hierarchy );
mNormal = vector;
// Now, calculate and store segment length
rmt::Vector segStart = (mCorners[0] + mCorners[3]) * 0.5f;
rmt::Vector segEnd = (mCorners[1] + mCorners[2]) * 0.5f;
mfSegmentLength = (segEnd - segStart).Length(); // *** SQUARE ROOT! ***
// Now, calculate and store the bounding sphere
rmt::Box3D box;
GetBoundingBox( &box ); // find the box on the fly (based on extents of corners)
// compute & store the bounding sphere based on bbox
rmt::Vector vectorBetween;
vectorBetween = ( box.high - box.low ) * 0.5f;
mSphere.centre = box.low + vectorBetween;
mSphere.radius = vectorBetween.Magnitude(); // *** SQUARE ROOT! ***
//////////////////////////////
// TODO:
// This stuff is dubious. It won't be accurate given that we can no
// longer assume interior and exterior edges are parallel.
//
// Now, Calculate the width of the leading edge of the segment.
float fWidth = segStart.Magnitude();
mfLaneWidth = fWidth / (float)rsd->GetNumLanes();
// Calculate a turn radius.
//
float fCosTheta = mEdgeNormals[0].DotProduct( mEdgeNormals[1] );
if ( fCosTheta < 0.0f )
{
fCosTheta = 0.0f - fCosTheta;
}
if ( fCosTheta < 0.001f ) //Clamp me.
{
fCosTheta = 0.0f;
}
rmt::Vector temp;
temp.Sub( mCorners[0], mCorners[1] );
float fInteriorEdgeLength = temp.Magnitude( );
temp.Sub( mCorners[2], mCorners[3] );
float fExteriorEdgeLength = temp.Magnitude( );
if ( fCosTheta != 0.0f )
{
// take the shortest length.
float length = ( fInteriorEdgeLength < fExteriorEdgeLength )? fInteriorEdgeLength : fExteriorEdgeLength;
length = length / 2.0f;
mfRadius = length / fCosTheta;
mfAngle = rmt::PI_BY2 - rmt::ACos( fCosTheta );
//rmt::RadianToDeg( mfAngle );
}
else
{
// Not a curved segment.
//
mfRadius = 0.0f;
mfAngle = 0.0f;
}
}
*/
void RoadSegment::Init( RoadSegmentData* rsd, rmt::Matrix& hierarchy, float scaleAlongFacing )
{
////////////////////////////////////////////////////////////////
// Transform segment data based on given matrix & scale-along-facing
//
rmt::Vector vector;
// store the unmodified values
for( int i=0; i<4; i++ )
{
vector = rsd->GetCorner( i );
mCorners[ i ] = vector;
vector = rsd->GetEdgeNormal( i );
mEdgeNormals[ i ] = vector;
}
//////////////////////////////
// TODO:
// This stuff is dubious. It won't be accurate given that we can no
// longer assume interior and exterior edges are parallel. AND
// somehow it's able to work quite accurately from the corner
// and edgenormal values that have not yet been transformed. *shudder*
//
// Now, Calculate the width of the leading edge of the segment.
float fWidth = ((mCorners[0] + mCorners[3]) * 0.5f).Magnitude();
mfLaneWidth = fWidth / (float)rsd->GetNumLanes();
// Calculate a turn radius.
//
float fCosTheta = mEdgeNormals[0].DotProduct( mEdgeNormals[1] );
if ( fCosTheta < 0.0f )
{
fCosTheta = 0.0f - fCosTheta;
}
if ( fCosTheta < 0.001f ) //Clamp me.
{
fCosTheta = 0.0f;
}
rmt::Vector temp;
temp.Sub( mCorners[0], mCorners[1] );
float fInteriorEdgeLength = temp.Magnitude( );
temp.Sub( mCorners[2], mCorners[3] );
float fExteriorEdgeLength = temp.Magnitude( );
if ( fCosTheta != 0.0f )
{
// take the shortest length.
float length = ( fInteriorEdgeLength < fExteriorEdgeLength )? fInteriorEdgeLength : fExteriorEdgeLength;
length = length / 2.0f;
mfRadius = length / fCosTheta;
mfAngle = rmt::PI_BY2 - rmt::ACos( fCosTheta );
//rmt::RadianToDeg( mfAngle );
}
else
{
// Not a curved segment.
//
mfRadius = 0.0f;
mfAngle = 0.0f;
}
///////////////////////////////////////////
// Ok, so first, transform the corners and the edgenormals
for( int i=0; i<4; i++ )
{
// transform the corner
vector = rsd->GetCorner( i );
vector.z *= scaleAlongFacing;
vector.Transform( hierarchy );
mCorners[ i ] = vector;
// transform the edge normals
vector = rsd->GetEdgeNormal( i );
vector.Rotate( hierarchy );
mEdgeNormals[ i ] = vector;
}
// Now, transform the segment normal
vector = rsd->GetSegmentNormal();
vector.Rotate( hierarchy );
mNormal = vector;
// Now, calculate and store segment length
rmt::Vector segStart = (mCorners[0] + mCorners[3]) * 0.5f;
rmt::Vector segEnd = (mCorners[1] + mCorners[2]) * 0.5f;
mfSegmentLength = (segEnd - segStart).Length(); // *** SQUARE ROOT! ***
// Now, calculate and store the bounding sphere
rmt::Box3D box;
GetBoundingBox( &box ); // find the box on the fly (based on extents of corners)
// compute & store the bounding sphere based on bbox
rmt::Vector vectorBetween;
vectorBetween = ( box.high - box.low ) * 0.5f;
mSphere.centre = box.low + vectorBetween;
mSphere.radius = vectorBetween.Magnitude(); // *** SQUARE ROOT! ***
}
void RoadSegment::GetBoundingBox(rmt::Box3D* box)
{
// get axis-aligned bounding box from vertices...
unsigned int numVertices = 4;
rmt::Vector vertex;
unsigned int i = 0;
for ( i = 0; i < numVertices; i++ )
{
vertex = mCorners[i];
if ( 0 == i )
{
// This is the first time.
// Initialize to some value.
//
box->low = box->high = vertex;
}
else
{
if ( box->low.x > vertex.x )
{
box->low.x = vertex.x;
}
if ( box->low.y > vertex.y )
{
box->low.y = vertex.y;
}
if ( box->low.z > vertex.z )
{
box->low.z = vertex.z;
}
if ( box->high.x < vertex.x )
{
box->high.x = vertex.x;
}
if ( box->high.y < vertex.y )
{
box->high.y = vertex.y;
}
if ( box->high.z < vertex.z )
{
box->high.z = vertex.z;
}
}
}
}
void RoadSegment::GetBoundingSphere(rmt::Sphere* sphere)
{
*sphere = mSphere;
}
/*
==============================================================================
RoadSegment::CalculateUnitDistIntoRoadSegment
==============================================================================
Description: Adapted from GameGems Article by Steven Ranck. pp412-pp420.
Implements a fast and simple algm for determing where a point
in between the edges of a 2D quad (RoadSegment). The result
is a unit floting point number, where 0 indicates that the point
lies on the leading edge, and where 1 indicates that the point
lies on the opposite edge. The RoadSegment may be any 4 sided
2D convex shape.
Constraints: The RoadSegment must be convex and have 4 sides.
The RoadSegment must have a non zero area.
The point must lie within the sector. ***** What if it doesn't?
Parameters: ( float fPointX, float fPointZ )
Return: A scalar from 0 to 1.
0 if point lies on the leading edge.
1 if point lies on the trailing edge.
Smoothly interpolated value for all points in between.
=============================================================================
*/
float RoadSegment::CalculateUnitDistIntoRoadSegment( float fPointX, float fPointZ )
{
rmt::Vector VLP, VTP;
float fDotL, fDotT;
// Get and cache the leading edge top corner
// and the trailing edge bottom corner.
//
rmt::Vector vertices[ 2 ];
GetCorner( 0, vertices[ 0 ] );
GetCorner( 2, vertices[ 1 ] );
// Get and cache the leading edge normal
// and the trailing edge normal.
//
rmt::Vector unitNormals[ 2 ];
GetEdgeNormal( 0, unitNormals[ 0 ] );
GetEdgeNormal( 2, unitNormals[ 1 ] );
//for this to work, the normals must both point into
//the volume...
//so, I need to reverse the second edge normal
unitNormals[1] *= -1;
// Compute vector from point on Leading Edge to P:
//
VLP.x = fPointX - vertices[0].x;
VLP.y = 0.0f;
VLP.z = fPointZ - vertices[0].z;
// Compute vector from point on Trailing Edge to P:
//
VTP.x = fPointX - vertices[1].x;
VTP.y = 0.0f;
VTP.z = fPointZ - vertices[1].z;
// Compute (VLP dot Leading Edge Normal):
//
fDotL = VLP.x*unitNormals[0].x + VLP.z*unitNormals[0].z;
// Compute (VTP dot Trailing Edge Normal):
//
fDotT = VTP.x*unitNormals[1].x + VTP.z*unitNormals[1].z;
// Compute unit distance into sector and return it:
//
return ( fDotL / (fDotL + fDotT) );
}
float RoadSegment::CalculateUnitHeightInRoadSegment( float fPointX, float fPointZ )
{
rmt::Vector VLP, VTP;
float fDotL, fDotT;
// Get and cache the leading edge top corner
// and the trailing edge bottom corner.
//
rmt::Vector vertices[ 2 ];
GetCorner( 0, vertices[ 0 ] );
GetCorner( 2, vertices[ 1 ] );
// Get and cache the leading edge normal
// and the trailing edge normal.
//
rmt::Vector unitNormals[ 2 ];
GetEdgeNormal( 1, unitNormals[ 0 ] );
GetEdgeNormal( 3, unitNormals[ 1 ] );
// Compute vector from point on Leading Edge to P:
//
VLP.x = fPointX - vertices[0].x;
VLP.y = 0.0f;
VLP.z = fPointZ - vertices[0].z;
// Compute vector from point on Trailing Edge to P:
//
VTP.x = fPointX - vertices[1].x;
VTP.y = 0.0f;
VTP.z = fPointZ - vertices[1].z;
// Compute (VLP dot Leading Edge Normal):
//
fDotL = VLP.x*unitNormals[0].x + VLP.z*unitNormals[0].z;
// Compute (VTP dot Trailing Edge Normal):
//
fDotT = VTP.x*unitNormals[1].x + VTP.z*unitNormals[1].z;
// Compute unit distance into sector and return it:
//
return ( fDotL / (fDotL + fDotT) );
}
/*
float RoadSegment::CalculateYHeight( float fPointX, float fPointZ )
{
// Get and cache the leading edge top corner
// and the trailing edge bottom corner.
//
rmt::Vector vertices[ 2 ];
GetCorner( 0, vertices[ 0 ] );
GetCorner( 2, vertices[ 1 ] );
float fDistance = CalculateUnitDistIntoRoadSegment( fPointX, fPointZ );
float y = LERP( fDistance, vertices[ 0 ].y, vertices[ 1 ].y );
return y;
}
*/
void RoadSegment::GetPosition( float t, float w, rmt::Vector* pos )
{
// Get and cache the corners.
//
rmt::Vector vertices[ 4 ];
GetCorner( 0, vertices[ 0 ] );
GetCorner( 1, vertices[ 1 ] );
GetCorner( 2, vertices[ 2 ] );
GetCorner( 3, vertices[ 3 ] );
rmt::Vector position;
// Interpolate the Normal vector across the Segment.
//
rmt::Vector leadingEdge;
leadingEdge.Sub( vertices[ 3 ], vertices[ 0 ] );
rmt::Vector leadingPoint = leadingEdge;
leadingPoint.Scale( w );
leadingPoint.Add( vertices[ 0 ] );
rmt::Vector trailingEdge;
trailingEdge.Sub( vertices[ 2 ], vertices[ 1 ] );
rmt::Vector trailingPoint = trailingEdge;
trailingPoint.Scale( w );
trailingPoint.Add( vertices[ 1 ] );
position.Sub( trailingPoint, leadingPoint );
position.Scale( t );
position.Add( leadingPoint );
*pos = position;
}
void RoadSegment::GetLaneLocation( float t, int index, rmt::Vector& position, rmt::Vector& facing )
{
//
// Get the world space point and facing at time 't'.
//
// Interpolate the facing.
//
rmt::Vector facingNormals[ 2 ];
GetEdgeNormal( 0, facingNormals[ 0 ] );
GetEdgeNormal( 2, facingNormals[ 1 ] );
facing.x = LERP( t, facingNormals[ 0 ].x, facingNormals[ 1 ].x );
facing.y = LERP( t, facingNormals[ 0 ].y, facingNormals[ 1 ].y );
facing.z = LERP( t, facingNormals[ 0 ].z, facingNormals[ 1 ].z );
// [Dusit: July 6th, 2003]
// NOTE:
// This is the CORRECT way to produce the lane length value when
// the width of the roadsegment isn't guaranteed across its length.
// The only thing that it assumes (and is always correct) is that
// each lane is as wide as the other lanes at any given point along
// the length of the segment
//
float edgeT = ((float)(index<<1) + 1.0f) / ((float)(GetNumLanes()<<1));
// find start & end points of the lane
rmt::Vector vec0, vec1, vec2, vec3;
GetCorner( 0, vec0 );
GetCorner( 1, vec1 );
GetCorner( 2, vec2 );
GetCorner( 3, vec3 );
// lane indices go from 0 to n, right to left ( n <=== 0 )
rmt::Vector bottomEdgeDir = vec0 - vec3; // points frm 3 to 0
rmt::Vector topEdgeDir = vec1 - vec2; // points frm 2 to 1
// now we figure out the starting point and ending point of the
// lane segment
rmt::Vector start = vec3 + bottomEdgeDir * edgeT;
rmt::Vector end = vec2 + topEdgeDir * edgeT;
// now find the t position along the lane
rmt::Vector laneDir = end - start;
position = start + laneDir * t;
/*
// Get and cache the corners.
//
rmt::Vector vertices[ 4 ];
GetCorner( 0, vertices[ 0 ] );
GetCorner( 1, vertices[ 1 ] );
GetCorner( 2, vertices[ 2 ] );
GetCorner( 3, vertices[ 3 ] );
//There is an assumption here that the road does not get wider or thinner
//across its length...
// Scale unnormalized vector by normalized center of desired lane.
// ( ( index * fLaneWidth ) + ( fLaneWidth / 2.0f ) ) / roadWidth;
//
float fCentreOfLane = ( index * mfLaneWidth ) + ( mfLaneWidth / 2.0f );
//I call this parametric variable w;
float w = fCentreOfLane / GetRoadWidth();
// Interpolate the Normal vector across the Segment.
//
rmt::Vector leadingEdge;
leadingEdge.Sub( vertices[ 0 ], vertices[ 3 ] );
rmt::Vector leadingPoint = leadingEdge;
leadingPoint.Scale( w );
leadingPoint.Add( vertices[ 3 ] );
rmt::Vector trailingEdge;
trailingEdge.Sub( vertices[ 1 ], vertices[ 2 ] );
rmt::Vector trailingPoint = trailingEdge;
trailingPoint.Scale( w );
trailingPoint.Add( vertices[ 2 ] );
//This gives the point between the leading and trailing point by parameter t.
position.x = LERP( t, leadingPoint.x, trailingPoint.x );
position.y = LERP( t, leadingPoint.y, trailingPoint.y );
position.z = LERP( t, leadingPoint.z, trailingPoint.z );
*/
}
/*
//==============================================================================
//RoadSegment::GetJoinPoint
//==============================================================================
//Description: RoadSegmentData pieces are always joined at the left corner
// of the trailing edge. The normal of the leading edge of the
// new piece is always the inverse normal of the trailing edge
// of the previous piece.
//
// This function returns the join vertex and facing in world space.
//
//Parameters: ( rmt::Vector& position, rmt::Vector& facing )
//
//Return: void
//
//=============================================================================
void RoadSegment::GetJoinPoint( rmt::Vector& position, rmt::Vector& facing )
{
// All segments are joined at the left top corner.
//
position = GetRoadSegmentData( )->GetCorner( 1 );
facing = GetRoadSegmentData( )->GetEdgeNormal( 2 );
facing.Scale( -1.0f );
}
*/
void RoadSegment::GetCorner( int index, rmt::Vector& out )
{
rAssert( 0 <= index && index < 4 );
out = mCorners[ index ];
}
void RoadSegment::GetEdgeNormal( int index, rmt::Vector& out )
{
rAssert( 0 <= index && index < 4 );
out = mEdgeNormals[ index ];
}
void RoadSegment::GetSegmentNormal( rmt::Vector& out )
{
out = mNormal;
}
unsigned int RoadSegment::GetNumLanes( void )
{
return GetRoad()->GetNumLanes();
}
float RoadSegment::GetLaneLength( unsigned int lane )
{
// [Dusit: July 6th, 2003]
// NOTE:
// This is the CORRECT way to produce the lane length value when
// the width of the roadsegment isn't guaranteed across its length.
// The only thing that it assumes (and is always correct) is that
// each lane is as wide as the other lanes at any given point along
// the length of the segment
//
// The problem with using this is that it requires a Sqrt (because
// it's too late for us to rearrange the data so that we can avoid
// this). So we continue using the old way because we're never off
// by more than 5 centimeters anyway.
//
float edgeT = ((float)(lane<<1) + 1.0f) / ((float)(GetNumLanes()<<1));
// find start & end points of the lane
rmt::Vector vec0, vec1, vec2, vec3;
GetCorner( 0, vec0 );
GetCorner( 1, vec1 );
GetCorner( 2, vec2 );
GetCorner( 3, vec3 );
// lane indices go from 0 to n, right to left ( n <=== 0 )
rmt::Vector bottomEdgeDir = vec0 - vec3; // points frm 3 to 0
rmt::Vector topEdgeDir = vec1 - vec2; // points frm 2 to 1
rmt::Vector start = vec3 + bottomEdgeDir * edgeT;
rmt::Vector end = vec2 + topEdgeDir * edgeT;
float expectedLength = (end - start).Magnitude();
return expectedLength;
/*
float computedLength = 0.0f;
// TODO:
// Because we can't assume that a lane is constant-width, this
// code is totally bogus...
if ( mfAngle > 0.0f )
{
// 2*PI*r for a circle.
// Theta*r for arc of theta degrees.
//
float fLaneOffset = mfLaneWidth * lane + mfLaneWidth;
computedLength = 2.0f * mfAngle * ( mfRadius + fLaneOffset );
}
else
{
// it's not a curve, both edge lengths will be equal.
//
computedLength = mfSegmentLength;
}
//rAssert( rmt::Epsilon( computedLength, expectedLength, 0.01f ) );
return computedLength;
*/
}
float RoadSegment::GetRoadWidth()
{
return mRoad->GetNumLanes() * mfLaneWidth;
}
/*
/////////////////////////////////////////////////////////////////
// TransformRoadSegment
/////////////////////////////////////////////////////////////////
//
TransformRoadSegment::TransformRoadSegment() :
RoadSegment( NULL )
{
mTransform.Identity();
mfScaleAlongFacing = 0.0f;
}
TransformRoadSegment::TransformRoadSegment(
const RoadSegmentData* pRoadSegmentData,
rmt::Matrix& transform )
:
RoadSegment( pRoadSegmentData )
{
mTransform = transform;
}
TransformRoadSegment::TransformRoadSegment(
const RoadSegmentData* pRoadSegmentData,
rmt::Matrix& transform,
float fScaleAlongFacing )
:
RoadSegment( pRoadSegmentData ),
mfScaleAlongFacing( fScaleAlongFacing )
{
mTransform = transform;
}
TransformRoadSegment::TransformRoadSegment(
const RoadSegmentData* pRoadSegmentData,
const rmt::Vector& facing,
const rmt::Vector& position )
:
RoadSegment( pRoadSegmentData )
{
rmt::Vector sApproximateUp( 0.0f, 1.0f, 0.0f );
mTransform.Identity( );
mTransform.FillHeading( facing, sApproximateUp );
mTransform.FillTranslate( position );
}
TransformRoadSegment::TransformRoadSegment(
const RoadSegmentData* pRoadSegmentData,
const rmt::Vector& facing,
const rmt::Vector& position,
float fScaleAlongFacing )
:
RoadSegment( pRoadSegmentData ),
mfScaleAlongFacing( fScaleAlongFacing )
{
rmt::Vector sApproximateUp( 0.0f, 1.0f, 0.0f );
mTransform.Identity( );
mTransform.FillHeading( facing, sApproximateUp );
mTransform.FillTranslate( position );
}
void TransformRoadSegment::GetCorner( int index, rmt::Vector& out ) const
{
out = GetRoadSegmentData( )->GetCorner( index );
out.z *= mfScaleAlongFacing;
out.Transform( mTransform );
}
void TransformRoadSegment::GetEdgeNormal( int index, rmt::Vector& out ) const
{
out = GetRoadSegmentData( )->GetEdgeNormal( index );
out.Rotate( mTransform );
}
void TransformRoadSegment::GetSegmentNormal( rmt::Vector& out ) const
{
out = GetRoadSegmentData( )->GetSegmentNormal( );
out.Rotate( mTransform );
}
//==============================================================================
//TransformRoadSegment::GetJoinPoint
//==============================================================================
//Description: RoadSegmentData pieces are always joined at the left corner
// of the trailing edge. The normal of the leading edge of the
// new piece is always the inverse normal of the trailing edge
// of the previous piece.
//
// This function returns the join vertex and facing in world space.
//
//Parameters: ( rmt::Vector& position, rmt::Vector& facing )
//
//Return: void
//
//=============================================================================
void TransformRoadSegment::GetJoinPoint( rmt::Vector& position, rmt::Vector& facing ) const
{
// All segments are joined at the left top corner.
//
facing = GetRoadSegmentData( )->GetEdgeNormal( 2 );
facing.Scale( -1.0f );
facing.Rotate( mTransform );
position = GetRoadSegmentData( )->GetCorner( 1 );
position.z *= mfScaleAlongFacing;
position.Transform( mTransform );
}
void TransformRoadSegment::GetBoundingBox(rmt::Box3D* box)
{
GetRoadSegmentData()->GetBoundingBox( *box );
(*box).high.z *= mfScaleAlongFacing;
(*box).high.Transform( mTransform );
(*box).low.Transform( mTransform );
}
void TransformRoadSegment::GetBoundingSphere(rmt::Sphere* sphere)
{
GetRoadSegmentData( )->GetBoundingSphere( *sphere );
(*sphere).radius *= mfScaleAlongFacing;
(*sphere).centre.Transform( mTransform );
}
void TransformRoadSegment::GetBoundingBox( rmt::Box3D& out ) const
{
GetRoadSegmentData( )->GetBoundingBox( out );
out.high.z *= mfScaleAlongFacing;
out.high.Transform( mTransform );
out.low.Transform( mTransform );
}
void TransformRoadSegment::GetBoundingSphere( rmt::Sphere& out ) const
{
out = GetRoadSegmentData( )->GetBoundingSphere( );
out.radius *= mfScaleAlongFacing;
out.centre.Transform( mTransform );
}
void TransformRoadSegment::GetTransform( rmt::Matrix &out ) const
{
out = mTransform;
}
*/