//============================================================================= // Copyright (C) 2002 Radical Entertainment Ltd. All rights reserved. // // File: geometry.cpp // // Description: Some linear algebra/geometry stuff mostly used in traffic // Also contains some useful structures. // // History: 09/09/2002 + Created -- Dusit Eakkachaichanvet // //============================================================================= #include #include /* ////////////////////////////////////////////////////////////////////////////// // OLD OLD OLD STUFF ////////////////////////////////////////////////////////////////////////////// bool fequals(float a, float b) { return (rmt::Fabs(a-b)<=MYEPSILON)? true: false; } bool fequals(float a, float b, float epsilon) { return (rmt::Fabs(a-b)<=epsilon)? true: false; } bool isVerticalLine( Line line ) { if( fequals(line.x1,line.x2) ) { return true; } return false; } Line getLine( float x1, float y1, float x2, float y2, bool isInfinite ) { Line line; line.x1 = x1; line.y1 = y1; line.x2 = x2; line.y2 = y2; line.isVertical = isVerticalLine( line ); if( !line.isVertical ) { line.slope = (line.y2 - line.y1)/(line.x2 - line.x1); line.b = line.y2 - line.slope * line.x2; } line.isInfinite = isInfinite; line.isFinishLine = false; return line; } bool isPointOnLine( Line line, Point p ) { // test first if point lies in line equation if( !line.isVertical ) { if( fequals(p.y, line.slope * p.x + line.b) ) { // Ok, so we've verified that p lies on the line. // now if the line is infinite, we're done if( line.isInfinite ) { return true; } // else test if point lies on line itself we always make sure // x1 < x2 and y1 < y2, swapping values as needed. if( line.x2 < line.x1 ) { float temp = line.x2; line.x2 = line.x1; line.x1 = temp; } if( line.y2 < line.y1 ) { float temp = line.y2; line.y2 = line.y1; line.y1 = temp; } // now check if p.x lies between acceptable values of x // and if p.y lies between acceptable values of y if( (line.x1 - MYEPSILON < p.x && p.x < line.x2 + MYEPSILON ) && (line.y1 - MYEPSILON < p.y && p.y < line.y2 + MYEPSILON ) ) { return true; } } } else { // if vertical line if( fequals(p.x,line.x1) ) { if( line.isInfinite ) { return true; } if( line.y2 < line.y1 ) { float temp = line.y2; line.y2 = line.y1; line.y1 = temp; } if( line.y1 - MYEPSILON < p.y && p.y < line.y2 + MYEPSILON ) { return true; } } } return false; } //============================================================================== // Helper function: IntersectLines2D //=============================================================================== // Description: Intersects two co-planar lines // // Parameters: (rmt::Vector) (rmt::Vector) (rmt::Vector) (rmt::Vector) (rmt::Vector&) // // Return: bool // //============================================================================== bool IntersectLines2D( rmt::Vector p1, rmt::Vector dir1, rmt::Vector p2, rmt::Vector dir2, rmt::Vector& p ) { // These will temporarily contain our intersection point coords // Remember that Point and Line work on x-y plane, not x-z plane // so let y = z // Point pTemp; rmt::Vector q1 = p1 + dir1; rmt::Vector q2 = p2 + dir2; Line line1 = getLine( p1.x, p1.z, q1.x, q1.z, true ); Line line2 = getLine( p2.x, p2.z, q2.x, q2.z, true ); // treat vertical cases separately if( line1.isVertical && line2.isVertical ) { return false; } else if( line1.isVertical && !line2.isVertical ) { // line1 is vertical and line2 is not, so pluck line1's // x-value into line2's equation to find y coord of // the potential intersection point. pTemp.x = line1.x1; pTemp.y = line2.slope * pTemp.x + line2.b; if( isPointOnLine(line1,pTemp) && isPointOnLine(line2,pTemp) ) { p.x = pTemp.x; p.y = p1.y; p.z = pTemp.y; return true; } return false; } //else if( !isVerticalLine(line1) && isVerticalLine(line2) ) else if( !line1.isVertical && line2.isVertical ) { // same as case above, but switch line1, line2 pTemp.x = line2.x1; pTemp.y = line1.slope * pTemp.x + line1.b; if( isPointOnLine(line1,pTemp) && isPointOnLine(line2,pTemp) ) { p.x = pTemp.x; p.y = p1.y; p.z = pTemp.y; return true; } return false; } else { //both non vertical if( line1.slope == line2.slope ) return false; // find intersection point pTemp.x = (line2.b-line1.b)/(line1.slope-line2.slope); pTemp.y = line1.slope* pTemp.x + line1.b; // make sure this point lies within the bounds of both lines if( isPointOnLine(line1,pTemp) && isPointOnLine(line2,pTemp) ) { p.x = pTemp.x; p.y = p1.y; p.z = pTemp.y; return true; } else { return false; } } return false; // just in case } */ ////////////////////////////////////////////////////////////////////////////////// // CUBIC BEZIER SHEEYATSU ////////////////////////////////////////////////////////////////////////////////// bool CubicBezier::sIsInitialized = false; float CubicBezier::B0[CubicBezier::MAX_CURVE_POINTS] = {0.0f}; float CubicBezier::B1[CubicBezier::MAX_CURVE_POINTS] = {0.0f}; float CubicBezier::B2[CubicBezier::MAX_CURVE_POINTS] = {0.0f}; float CubicBezier::B3[CubicBezier::MAX_CURVE_POINTS] = {0.0f}; void CubicBezier::InitOnceLUTs() { if( sIsInitialized ) { return; } float bias = 1.0f / (float)(MAX_CURVE_POINTS - 1); int i = MAX_CURVE_POINTS - 1; float t = 0.0f, t2, t3; for( i = MAX_CURVE_POINTS-1; i>=0; i-- ) { t2 = t*t; t3 = t2*t; // Fill the LUTs B0[i] = t3; B1[i] = 3.0f * t2 * (1.0f-t); B2[i] = 3.0f * t * (1.0f-t)*(1.0f-t); B3[i] = (1.0f-t)*(1.0f-t)*(1.0f-t); t += bias; } rAssert( i == -1); sIsInitialized = true; } CubicBezier::CubicBezier() { if( !CubicBezier::sIsInitialized ) { CubicBezier::InitOnceLUTs(); } mCurveIsCreated = false; mCurveIsCreated2D = false; mNumControlPointsAdded = 0; rmt::Vector dummy( 0.0f, 0.0f, 0.0f ); int i; for( i=0; iClear(); } void DListArray::Clear() { int i=0; for( i ; i<(MAX_ELEMS-1) ; ++i ) { mElems[i].data = NULL; mElems[i].next = i+1; mElems[i].prev = -1; } mElems[i].data = NULL; mElems[i].next = -1; mElems[i].prev = -1; mHead = -1; mTail = -1; mFree = 0; mnElems = 0; } // returns the index value of the newly added element // or -1 on error int DListArray::AddLast( void* data ) { assert( data != NULL ); if( mFree == -1 ) { return -1; } mElems[mFree].data = data; mElems[mFree].prev = mTail; if( mnElems > 0 ) { mElems[mTail].next = mFree; } else { mHead = mFree; } mTail = mFree; mFree = mElems[mFree].next; mElems[mTail].next = -1; mnElems++; return mTail; } // returns the index value of the newly added element // or -1 on error int DListArray::AddFirst( void* data ) { assert( data != NULL ); if( mFree == -1 ) { return -1; } int newIndex = mFree; mFree = mElems[mFree].next; mElems[newIndex].data = data; mElems[newIndex].next = mHead; if( mnElems > 0 ) { mElems[mHead].prev = newIndex; } else { mTail = newIndex; } mHead = newIndex; mnElems++; return mHead; } // returns index value of the newly inserted element // or -1 on error int DListArray::InsertAfter( void* data, int i ) { assert( mnElems >= 1 ); assert( data != NULL ); assert( 0 <= i && i < MAX_ELEMS ); assert( mElems[i].data != NULL ); if( mFree == -1 ) { return -1; } int newIndex = mFree; mFree = mElems[mFree].next; mElems[newIndex].data = data; mElems[newIndex].prev = i; mElems[newIndex].next = mElems[i].next; if( mElems[i].next != -1 ) { mElems[ mElems[i].next ].prev = newIndex; } else { mTail = newIndex; } mElems[i].next = newIndex; mnElems++; return newIndex; } // bool DListArray::Remove( void* data ) { assert( data != NULL ); int i = 0; bool res = false; for( i ; i 0.0f ) { // target is in front, which is a concern... float lateralDist = mySide.Dot( toTarget ); if( lateralDist > 0.0f ) { targetOnMyRightSide = true; } else { targetOnMyRightSide = false; } lateralDist = rmt::Fabs( lateralDist ); // if target is in our path if( lateralDist <= myRadius ) { return true; } } } return false; } rmt::Vector UpdateVUP( const rmt::Vector& position, const rmt::Vector& target ) { const float epsilon = 0.01f; //Set the vUP by projecting the heading into the ZX plane and creating a //crossproduct of a right angle to the projected heading along the X axis //and the heading. rmt::Vector X, Y, Z; Z.Sub(target, position); X.Set(Z.z, 0, -Z.x); if ( rmt::Epsilon( X.x, 0, epsilon ) && rmt::Epsilon( X.y, 0, epsilon ) && rmt::Epsilon( X.z, 0, epsilon ) ) { //Then the camera is looking straight down. Y.Set( 0, 0, 1.0f ); //Up along the Z... } else { Y.CrossProduct(Z, X); Y.Normalize(); // *** SQUARE ROOT! *** } return Y; } // Given points P1 and P2, and two points that define a line, A and B // P1 and P2 are on the same side of the line, if the Normals for BAxP1A // and BAxP2A are pointing on the same side of the plane (i.e. // N1-dot-N2 >= 0) bool PointsOnSameSideOfLine( const rmt::Vector& P1, const rmt::Vector& P2, const rmt::Vector& A, const rmt::Vector& B ) { rmt::Vector BA, P1A, P2A, crossP1, crossP2; BA.Sub( B, A ); P1A.Sub( P1, A ); P2A.Sub( P2, A ); crossP1.CrossProduct( BA, P1A ); crossP2.CrossProduct( BA, P2A ); if( crossP1.Dot(crossP2) >= 0 ) { return true; } return false; } // Given triangle with vertices v1, v2, v3 and a point p // p is inside triangle if it is on the same side of line v1v2 as v3 // and on the same side of line v2v3 as v1, // and on the same side of line v1v3 as v2 // // NOTE: Because we use Which-Side-of-Line solution, the point // that is "within" a triangle is not necessarily on the // same plane as the triangle (it could still be above or // below the actual triangle, as long as it lies within the infinite // planes formed by the 3 sides of the triangle. // bool PointLiesInTriangle ( const rmt::Vector& p, const rmt::Vector& v1, const rmt::Vector& v2, const rmt::Vector& v3 ) { if( PointsOnSameSideOfLine( p, v1, v2, v3 ) && PointsOnSameSideOfLine( p, v2, v1, v3 ) && PointsOnSameSideOfLine( p, v3, v1, v2 ) ) { return true; } return false; } rmt::Vector GetProjectionVector( const rmt::Vector& source, const rmt::Vector& target ) { return target * ( target.Dot(source) / target.Dot(target) ); } // In Lefthand coordinate system, turn vector to // the left (counter-clockwise) 90 degrees about Y axis & return new vector rmt::Vector Get90DegreeLeftTurn( const rmt::Vector& orig ) { rmt::Vector newVec; newVec.Set( -1 * orig.z, orig.y, orig.x ); return newVec; } // In Lefthand coordinate system, turn vector to // the right (clockwise) 90 degrees about Y axis & return new vector rmt::Vector Get90DegreeRightTurn( const rmt::Vector& orig ) { rmt::Vector newVec; newVec.Set( orig.z, orig.y, -1 * orig.x ); return newVec; } float GetRotationAboutY( float x, float z ) { float angle = 0.0f; if( rmt::Epsilon(x, 0.0f) && rmt::Epsilon(z, 0.0f) ) { angle = 0.0f; } else { // generate angle from x-z vector // - assumes DEFAULT_FACING_VECTOR is (0,0,-1) // - assumes UP VECTOR is (0,1,0) angle = rmt::ATan2(x, z); if( rmt::IsNan(angle) ) { angle = 0.0f; } /* angle = rmt::ATan2(-x, -z); // wrap to [0, 2*pi) if (angle < 0.0f) { angle += rmt::PI_2; } */ } return angle; } bool PointToLineProjection2D( const rmt::Vector& inPt, const rmt::Vector& linePt1, const rmt::Vector& linePt2, rmt::Vector& outPt ) { // The beauty of calling this 2D function is that we break it down into components // so we don't do unnecessary float ops for the y values float x1,x2,x3,z1,z2,z3; x1 = linePt1.x; x2 = linePt2.x; x3 = inPt.x; z1 = linePt1.z; z2 = linePt2.z; z3 = inPt.z; // make sure we were given a line, not a point dammit! rAssertMsg( !( rmt::Epsilon( x1,x2,0.0005f ) && rmt::Epsilon( z1,z2,0.0005f )), "PointToLineProjection2D: The two points of the \"line\" are too close together.\n" ); // THEORY // ====== // Let outPt be the point resulting from projecting inPt onto // linesegment [linePt2 - linePt1]: // // 1) outPt = linePt1 + u * [linePt2 - linePt1]; // // Since the projection is at Right Angle, we can say that the lines // [linePt2 - linePt1] and [inPt - outPt] have zero-length dotproduct: // // 2) [inPt - outPt] dot [linePt2 - linePt1] = 0 // // Substituting 1) into 2) gives: // // 3) [ inPt - [linePt1+u*[linePt2 - linePt1]] ] dot [linePt2 - linePt1] = 0 // // Solving for u gives: // float x2MINUSx1 = x2-x1; float z2MINUSz1 = z2-z1; float u = ( (x3-x1)*x2MINUSx1 + (z3-z1)*z2MINUSz1 ) / ( (x2MINUSx1*x2MINUSx1)+(z2MINUSz1*z2MINUSz1) ); // Populate the return point outPt.Set( x1 + u*x2MINUSx1, inPt.y, z1 + u*z2MINUSz1 ); // If u is not on the line segment, outPt will not be in bounds if( u < 0.0f || u > 1.0f ) { return false; } return true; } bool PointOnLeftSideOfLine( const rmt::Vector& p, const rmt::Vector& start, const rmt::Vector& end ) { rmt::Vector start2p = p - start; rmt::Vector start2end = end - start; rmt::Vector leftVec = Get90DegreeLeftTurn( start2end ); float dp = leftVec.Dot( start2p ); if( dp > 0.0f ) // +ve means on the left { return true; } return false; } bool PointOnRightSideOfLine( const rmt::Vector& p, const rmt::Vector& start, const rmt::Vector& end ) { rmt::Vector start2p = p - start; rmt::Vector start2end = end - start; rmt::Vector rightVec = Get90DegreeRightTurn( start2end ); float dp = rightVec.Dot( start2p ); if( dp > 0.0f ) // +ve means on the right { return true; } return false; } float FindClosestPointOnLine( const rmt::Vector& start, const rmt::Vector& end, const rmt::Vector& p, rmt::Vector& closestPt ) { rmt::Vector start2p = p - start; rmt::Vector lDir = end - start; float lDirMagSqr = lDir.Dot(lDir); // if end and start are basically the same point, // then lDir is zero. So the closest point returned // is that point // if( rmt::Epsilon( lDirMagSqr, 0.0f, 0.001f ) ) { closestPt = end; return 0.0f; } float scale = lDir.Dot( start2p ) / lDirMagSqr; if( scale > 1.0f ) { closestPt = end; } else if( scale < 0.0f ) { closestPt = start; } else { closestPt = start + lDir * scale; } return scale; } float GetLineSegmentT ( const rmt::Vector& segStart, const rmt::Vector& segEnd, const rmt::Vector& pt ) { float e = 0.0001f; #if defined( RAD_DEBUG ) || defined( RAD_TUNE ) // make sure this point is on the line rmt::Vector closestPt; FindClosestPointOnLine( segStart, segEnd, pt, closestPt ); rAssert( closestPt.Equals( pt, e ) ); #endif float segT = 0.0f; if( rmt::Epsilon( segEnd.x, segStart.x, e ) ) { if( rmt::Epsilon( segEnd.y, segStart.y, e ) ) { if( rmt::Epsilon( segEnd.z, segStart.z, e ) ) { segT = 0.0f; } else { segT = (pt.z - segStart.z) / (segEnd.z - segStart.z); } } else { segT = (pt.y - segStart.y) / (segEnd.y - segStart.y); } } else { segT = (pt.x - segStart.x) / (segEnd.x - segStart.x); } rAssert( 0.0f <= segT && segT <= 1.0f ); return segT; }