/* ====== CUBIC HERMITE SPLINES ======= The difference between Hermite splines and Bezier curves is that Hermite splines go through all their nodes. Bezier splines touch their end points but use the other nodes to create tangents without actually going through them. Vehicle splines aren't bezier, but they cut corners too. The advantage of these hermite cubic splines is that it's easy to know precisely how long it will take to travel the path. Hermite spline code, from http://www.siggraph.org/education/materials/HyperGraph/modeling/splines/hermite.htm and http://cubic.org/docs/hermite.htm Given : Points P1, P4 Tangent vectors R1, R4 on the interval n = 0 to 1. We want to make a cubic curve that goes through the two points with the correct direction at each point. Consider each dimension separately. Here's just the x component: x(n) = axn^3 + bxn^2 + cxn + dx x(n) = P1x(2n^3 - 3n^2 + 1) + P4x(-2n^3 + 3n^2) + R1x(n^3 - 2n^2 + n) + R4x(n^3 - n^2) So here's the matrix in a form that is easily usable to precompute coefficients x(n) = ( 2*P1x - 2*P4x + R1x + R4x ) * n^3 + ( -3*P1x + 3*P4x - 2*R1x - R4x ) * n^2 + ( R1x ) * n + ( P1x ) ------------------------------- Tangents Since we're only given the points, we need to decide on some tangents. For the end points, I'll set them so the acceleration is 0. For the others, I'll use Cardinal splines: R[i] = (1-tension) * ( P[i+1] - P[i-1] ) It's not that simple though, since the time between points is variable. R[i] = R[i] * ( 2L[i-1] / ( L[i-1] + L[i] ) ) where L[i] is the length of the segment after R[i] ------------------------------- */ #include common_scripts\utility; /* ====== Cubic Hermite Spline - Calculate Cardinal Tangent. ====== * Given the points before and after and the length of the segments before and after, return the tangent at this point. * Set tension to 0.5 for a Catmull-Rom spline. * nb Returns an "incoming" and an "outgoing" tangent. They are the same tangent really, but scaled by the lengths so * that when they are used with a normalized length in csplineSeg_calcCoeffs, they give a consistent result. */ cspline_calcTangent(P1, P3, Length1, Length2, tension) { incoming = []; outgoing = []; for (i=0; i<3; i++) { incoming[i] = ( 1 - tension ) * ( P3[i] - P1[i] ); outgoing[i] = incoming[i]; incoming[i] *= ( 2*Length1 / ( Length1 + Length2 ) ); outgoing[i] *= ( 2*Length2 / ( Length1 + Length2 ) ); } R = []; R["incoming"] = ( incoming[0], incoming[1], incoming[2] ); R["outgoing"] = ( outgoing[0], outgoing[1], outgoing[2] ); return R; } /* ====== Cubic Hermite Spline - Calculate Kochanek-Bartels Tangent. ====== * Given the points before and after and the length of the segments before and after, return the tangent at this point. * nb Returns an "incoming" and an "outgoing" tangent. They are the same tangent really, but scaled by the lengths so * that when they are used with a normalized length in csplineSeg_calcCoeffs, they give a consistent result. */ cspline_calcTangentTCB(P1, P2, P3, Length1, Length2, t, c, b) { incoming = []; outgoing = []; for (i=0; i<3; i++) { incoming[i] = ( 1 - t ) * ( 1 - c ) * ( 1 + b) * 0.5 * ( P2[i] - P1[i] ); incoming[i] += ( 1 - t ) * ( 1 + c ) * ( 1 - b) * 0.5 * ( P3[i] - P2[i] ); incoming[i] *= ( 2*Length1 / ( Length1 + Length2 ) ); outgoing[i] = ( 1 - t ) * ( 1 + c ) * ( 1 + b) * 0.5 * ( P2[i] - P1[i] ); outgoing[i] += ( 1 - t ) * ( 1 - c ) * ( 1 - b) * 0.5 * ( P3[i] - P2[i] ); outgoing[i] *= ( 2*Length2 / ( Length1 + Length2 ) ); } R = []; R["incoming"] = ( incoming[0], incoming[1], incoming[2] ); R["outgoing"] = ( outgoing[0], outgoing[1], outgoing[2] ); return R; } /* ====== Cubic Hermite Spline - Calculate Natural Tangent ====== * Given two points and a tangent at one of them, returns a tangent for the other that will result in 0 acceleration at * that point. Note that: * - it doesn't matter if tangent is for the start or end of the segment; the result is the same * - the return value is an array with identical components "incoming" and "outgoing". */ cspline_calcTangentNatural(P1, P2, R1) { // As I understand it, natural splines are those which have constant velocity at the endpoints. // For no accn at P1, we need // -3P1 + 3P2 - 2R1 - R2 = 0 // R1[i] = ( -3*P1[i] + 3*P2[i] - R2[i] ) / 2 // For no accn at P2, we need ( -3*P1[i] + 3*P2[i] - 2*R1[i] - R2[i] ) + 3*( 2*P1[i] - 2*P2[i] + R1[i] + R2[i] ) = 0 // -3P1 + 3P2 - 2R1 - R2 + 6P1 - 6P2 + 3R1 + 3R2 = 0 // 3P1 - 3P2 + R1 + 2R2 = 0 // R2[i] = ( -3*P1[i] + 3*P2[i] - R1[i] ) / 2 numDimensions = 3; incoming = []; outgoing = []; if ( IsDefined( R1 ) ) { for (i=0; i 1 ) { segLength *= topSpeed; R1 /= topSpeed; // Keep the tangents the same. We already know they're not too fast. This isn't mathematically R2 /= topSpeed; // correct but it produces a nice curve. // And we need to recalculate the segment csSeg = csplineSeg_calcCoeffs( P1, P2, R1, R2 ); } //prof_end("csplines_topspeed"); csSeg.endAt = segLength; return csSeg; } // Returns the positions, velocities and accelerations of the node points along the spline path. cspline_getNodes(csPath) { array = []; segLength = csPath.Segments[0].endAt; array[0] = csplineSeg_getPoint( csPath.Segments[0], 0, segLength, csPath.Segments[0].speedStart ); array[0]["time"] = 0; startDist = 0; for (segNum=0; segNum < csPath.Segments.size; segNum++) { segLength = csPath.Segments[segNum].endAt - startDist; array[segNum+1] = csplineSeg_getPoint( csPath.Segments[segNum], 1, segLength, csPath.Segments[segNum].speedEnd ); posVelStart = csplineSeg_getPoint( csPath.Segments[segNum], 0, segLength, csPath.Segments[segNum].speedStart ); array[segNum]["acc_out"] = posVelStart["acc"]; array[segNum+1]["time"] = csPath.Segments[segNum].endTime; startDist = csPath.Segments[segNum].endAt; } array[csPath.Segments.size]["acc_out"] = array[csPath.Segments.size]["acc"]; return array; } /* ============= ///ScriptDocBegin "Name: csplineSeg_getPoint( , , , )" "Summary: Given a Hermite Spline segment and a normalized distance along the segment, return position, velocity and acceleration vectors." "Module: CSplines" "CallOn: nothing" "MandatoryArg: : a cubic Hermite spline segment - a struct containing n3, n2, n and c, each of which is an array of 3 floats." "MandatoryArg: : The distance along the segment. 0 <= x <= 1." "OptionalArg: : The length of this segment, used for calculating the correct velocity and acceleration." "OptionalArg: : The current rate of movement along the path, also used for correcting the velocity and acceleration." "Example: posVelArray = csplineSeg_getPoint( csPath.Segments[segNum], normalizedDistance );" "SPMP: both" ///ScriptDocEnd ============= */ csplineSeg_getPoint(csplineSeg, x, segLength, speedMult ) { numDimensions = 3;//csplineSeg.n3.size; posArray = []; velArray = []; accArray = []; returnArray = []; for (i=0; i 0 && cubicRoots[0] < 1 ) { slope = (2*b*cubicRoots[0]) + c; if ( slope < 0 ) values[values.size] = cubicRoots[0]; } if ( IsDefined( cubicRoots[1] ) && cubicRoots[1] > 0 && cubicRoots[1] < 1 ) { slope = (2*b*cubicRoots[0]) + c; if ( slope < 0 ) values[values.size] = cubicRoots[1]; } } else { // Divide the interval up into sub-intervals based on roots of the cubic's derivative. // Within each sub-interval, we know the cubic is constantly increasing or constantly decreasing, so there can only be one // root at most. Then within those intervals that are decreasing we can quickly check whether or not there is a root, and only // then do we have to actually find it. quadRoots = maps\interactive_models\_interactive_utility::rootsOfQuadratic( 3*a, 2*b, c ); i = 0; points[0] = 0; for ( i=0; i< quadRoots.size; i++ ) { if ( quadRoots[i] > 0 && quadRoots[i] < 1 ) { points[points.size] = quadRoots[i]; } } points[points.size] = 1; for ( i=1; i 0 ) && (endVal < 0) ) { // There's a cubic root in the interval that corresponds to a maximum of the quartic. values[values.size] = maps\interactive_models\_interactive_utility::newtonsMethod( x0, x1, a, b, c, d, 0.02 ); } } } values[values.size] = 1; // Now check through the contenders we have and see which has the highest speed. // Switch our variables over to the quartic that represents the square of the speed a = 9*n3_n3; b = 12*n3_n2; c = 6*n3_n + 4*n2_n2; d = 4*n2_n; e = n_n; maxSpeedSq = 0; foreach ( x in values ) { speedSq = ( a*x*x*x*x ) + ( b*x*x*X ) + ( c*x*x ) + ( d*x ) + e; if ( speedSq > maxSpeedSq ) maxSpeedSq = speedSq; /*/# // Check my math. actualPoint = csplineSeg_getPoint( csplineSeg, x ); actualSpeedSq = LengthSquared( actualPoint["vel"] ); actualSpeedSq *= 1; // Just so I can get a breakpoint here. #/*/ } return ( sqrt( maxSpeedSq ) / segLength ); } csplineSeg_calcLengthByStepping( csplineSeg, numSteps ) { oldPos = csplineSeg_getPoint( csplineSeg, 0 ); distance = 0; for ( i=1; i <= numSteps; i++ ) { n = i / numSteps; newPos = csplineSeg_getPoint( csplineSeg, n ); distance += Length( oldPos["pos"] - newPos["pos"] ); oldPos = newPos; } return distance; } csplineSeg_calcTopSpeedByStepping( csplineSeg, numSteps, segLength ) { oldPos = csplineSeg_getPoint( csplineSeg, 0 ); topSpeed = 0; for ( i=1; i <= numSteps; i++ ) { n = i / numSteps; newPos = csplineSeg_getPoint( csplineSeg, n ); distance = Length( oldPos["pos"] - newPos["pos"] ); if ( distance > topSpeed ) topSpeed = distance; oldPos = newPos; } topSpeed *= numSteps / segLength ; return topSpeed; } /* Take a targetname for the first node in a path and return an array with positions for all nodes */ /* ============= ///ScriptDocBegin "Name: cspline_findPathnodes( )" "Summary: Take a targetname for the first node in a path and return an array of nodes." "Module: CSplines" "CallOn: nothing" "MandatoryArg: : The targetname of the first node in the path. Currently requires a path of vehicle nodes." "Example: csPath = cspline_findPathnodes( targetname );" "SPMP: both" ///ScriptDocEnd ============= */ cspline_findPathnodes( first_node ) // Adapted from waterball_get_pathnodes in flood_flooding.gsc { next_node = first_node; array = []; for( node_num = 0; IsDefined( next_node.target ); node_num++ ) { array[node_num] = next_node; targetname = next_node.target; next_node = GetNode( targetname, "targetname" ); if ( !IsDefined( next_node ) ) { next_node = GetVehicleNode( targetname, "targetname" ); if ( !IsDefined( next_node ) ) { next_node = GetEnt( targetname, "targetname" ); if ( !IsDefined( next_node ) ) { next_node = getstruct( targetname, "targetname" ); } } } AssertEx( IsDefined(next_node), "cspline_findPathnodes: Couldn't find targetted node with targetname "+targetname+"." ); } array[node_num] = next_node; return( array ); } /* ============= ///ScriptDocBegin "Name: cspline_makePath1Seg( , , , )" "Summary: Take two points and returns a data structure with a single-piece hermite spline path defined in it. "Module: CSplines" "CallOn: nothing" "MandatoryArg: : Point to start from." "MandatoryArg: : Point to end at." "OptionalArg: : Velocity vector for the beginning of the spline; indicates movement in one frame. Will use natural (ie zero acceleration) if not supplied." "OptionalArg: : Velocity vector for the end of the spline; indicates movement in one frame. Will use natural (ie zero acceleration) if not supplied." "Example: csPath = cspline_makePath1Seg( self.origin, targetPoint, currentVelocity );" "SPMP: both" ///ScriptDocEnd ============= */ cspline_makePath1Seg(startOrg, endOrg, startVel, endVel) { nodes = []; nodes[0] = SpawnStruct(); nodes[0].origin = startOrg; if ( IsDefined(startVel) ) { nodes[0].speed = Length( startVel ); startVel /= nodes[0].speed; nodes[0].speed *= 20; // Speeds of real nodes are inches per second; velocity is per frame. } else { nodes[0].speed = 20; } nodes[1] = SpawnStruct(); nodes[1].origin = endOrg; if ( IsDefined(endVel) ) { nodes[1].speed = Length( endVel ); endVel /= nodes[1].speed; nodes[1].speed *= 20; } else { nodes[1].speed = 20; } return cspline_makePath( nodes, true, startVel, endVel ); } /* ============= ///ScriptDocBegin "Name: cspline_makePathToPoint( , , , )" "Summary: Take two end points and returns a data structure with a fairly smooth hermite spline path defined in it. "Module: CSplines" "CallOn: nothing" "MandatoryArg: : Point to start from." "MandatoryArg: : Point to end at." "OptionalArg: : Velocity vector for the beginning of the spline; indicates movement in one frame. Will use natural (ie zero acceleration) if not supplied." "OptionalArg: : Velocity vector for the end of the spline; indicates movement in one frame. Will use natural (ie zero acceleration) if not supplied." "OptionalArg: : forces the creation of two intermediate nodes, making a 3-segment path. These nodes are normally created only if the velocities do not line up with the direction of the path." "Example: csPath = cspline_makePathToPoint( self.origin, targetOrigin, currentVelocity );" "SPMP: both" ///ScriptDocEnd ============= */ cspline_makePathToPoint(startOrg, endOrg, startVel, endVel, forceCreateIntermediateNodes) { dirs = []; if ( !IsDefined( forceCreateIntermediateNodes ) ) forceCreateIntermediateNodes = false; if ( IsDefined(startVel) ) { startSpeed = Length( startVel ); dirs[0] = startVel / startSpeed; startSpeed *= 20; // Speeds of real nodes are inches per second; velocity is per frame. } else { startSpeed = 20; } if ( IsDefined(endVel) ) { endSpeed = Length( endVel ); dirs[1] = endVel / endSpeed; endSpeed *= 20; // Speeds of real nodes are inches per second; velocity is per frame. } else { endSpeed = 20; } if ( ( startSpeed / endSpeed > 1.2 ) || ( endSpeed / startSpeed > 1.2 ) || ( forceCreateIntermediateNodes ) ) { if ( !IsDefined( dirs[0] ) ) dirs[0] = (0,0,0); if ( !IsDefined( dirs[1] ) ) { dirs[1] = (0,0,0); } } pathVec = endOrg - startOrg; pathLength = Length( pathVec ); pathDir = pathVec / pathLength; nodes = []; nodes[0] = SpawnStruct(); nodes[0].origin = startOrg; nodes[0].speed = startSpeed; // Find an offset based on the start/end velocity, and make sure it's a good distance from the straight line path offsetLengths = []; midSpeed = max( startSpeed, endSpeed ); if ( IsDefined( dirs[0] ) ) { offsetLengths[0] = ( startSpeed + midSpeed ) / ( 2 * 20 ); } if ( IsDefined( dirs[1] ) ) { offsetLengths[1] = ( endSpeed + midSpeed ) / ( 2 * 20 ); } for (i=0; i<2; i++) { if ( IsDefined( dirs[i]) ) { sign = ( 0.5 - i ) * 2; // 1 or -1 offsetVec = dirs[i]; offsetVec *= sign; offsetDotPath = VectorDot( offsetVec, pathDir ); // Only create a new node if the direction doesn't line up with the straight line path, or if there is a significant speed difference. if ( ( offsetDotPath * sign < 0.3 ) || ( startSpeed / endSpeed > 1.2 ) || ( endSpeed / startSpeed > 1.2 ) || forceCreateIntermediateNodes ) { // If the velocity goes against the direction of the path, make sure the new point is out wide if ( offsetDotPath * sign < 0 ) { offsetAlongPath = offsetDotPath * pathDir; offsetVec -= offsetAlongPath; AssertEx( VectorDot( offsetVec, pathDir ) == 0, "Dot result should be 0: "+VectorDot( offsetVec, pathDir ) ); offsetVec = VectorNormalize( offsetVec ); offsetVec += offsetAlongPath; } // Now move the new point along the path a bit offsetVec += pathDir * sign; offsetVec = offsetVec * offsetLengths[i]; offsetVec *= sqrt(pathLength) * 2; nodes[nodes.size ] = SpawnStruct(); if ( i==0 ) { nodes[nodes.size-1].origin = startOrg + offsetVec; } else { nodes[nodes.size-1].origin = endOrg + offsetVec; } nodes[nodes.size-1].speed = midSpeed ; } } } n = nodes.size; nodes[n] = SpawnStruct(); nodes[n].origin = endOrg; nodes[n].speed = endSpeed; /# if ( GetDvarInt( "interactives_debug" ) ) { thread draw_line_for_time ( startOrg, endOrg, 0, .7, .7, 1 ); for ( n=1; n, , )" "Summary: Take an array of nodes in a path and return a data structure with the hermite spline path defined in it." "Module: CSplines" "CallOn: nothing" "MandatoryArg: : An array of nodes (or any structs with .origin fields)." "OptionalArg: : Use the speed keypairs from the nodes." "OptionalArg: : Velocity (or tangent) vector for the beginning of the spline. Will use natural (ie zero acceleration) if not supplied." "OptionalArg: : Velocity (or tangent) vector for the end of the spline. Will use natural (ie zero acceleration) if not supplied." "OptionalArg: : Can be set to false to avoid the expensive speed capping calculation. If false, speed between nodes will exceed the speed set at the nodes." "Example: csPath = cspline_makePath( targetname );" "SPMP: both" ///ScriptDocEnd ============= */ cspline_makePath(nodes, useNodeSpeeds, startVel, endVel, capSpeed) { //prof_begin("cspline_makePath"); csPath = SpawnStruct(); csPath.Segments = []; if (!IsDefined( useNodeSpeeds ) ) useNodeSpeeds = false; AssertEx( !useNodeSpeeds || IsDefined( nodes[0].speed ), "cspline_makePath: Speed keypair required for first node in path (node at "+nodes[0].origin+")" ); if (!IsDefined( capSpeed ) ) capSpeed = true; AssertEx( IsDefined( nodes[0] ), "cspline_makePath: No nodes supplied" ); AssertEx( IsDefined( nodes[1] ), "cspline_makePath: Only one node supplied" ); path_length = 0; nextTangent = []; nextSegLength = Distance( nodes[0].origin, nodes[1].origin ); while ( IsDefined( nodes[csPath.Segments.size+2] ) ) { i = csPath.Segments.size; prevPoint = nodes[i].origin; nextPoint = nodes[i+1].origin; nextNextPoint = nodes[i+2].origin; thisSegLength = nextSegLength; nextSegLength = Distance( nodes[i+1].origin, nodes[i+2].origin ); prevTangent = nextTangent; nextTangent = cspline_calcTangent( prevPoint, nextNextPoint, thisSegLength, nextSegLength, 0.5 ); AssertEx( abs( Length( nextTangent["incoming"] ) ) <= thisSegLength, "cspline_makePath: Tangent slope is > 1. This shouldn't be possible." ); AssertEx( abs( Length( nextTangent["outgoing"] ) ) <= nextSegLength, "cspline_makePath: Tangent slope is > 1. This shouldn't be possible." ); if (i==0) { if ( IsDefined( startVel ) ) { prevTangent["outgoing"] = startVel * thisSegLength; } else { prevTangent = cspline_calcTangentNatural( prevPoint, nextPoint, nextTangent["incoming"] ); } } if ( capSpeed ) { csPath.Segments[i] = csplineSeg_calcCoeffsCapSpeed( prevPoint, nextPoint, prevTangent["outgoing"], nextTangent["incoming"], thisSegLength ); path_length += csPath.Segments[i].endAt; } else { csPath.Segments[i] = csplineSeg_calcCoeffs( prevPoint, nextPoint, prevTangent["outgoing"], nextTangent["incoming"] ); path_length += thisSegLength; } csPath.Segments[i].endAt = path_length; } i = csPath.Segments.size; prevPoint = nodes[i].origin; nextPoint = nodes[i+1].origin; thisSegLength = nextSegLength; prevTangent = nextTangent; if ( i==0 && IsDefined( startVel ) ) { prevTangent["outgoing"] = startVel * thisSegLength; } if ( IsDefined( endVel ) ) { nextTangent["incoming"] = endVel * thisSegLength; } else { nextTangent = cspline_calcTangentNatural(prevPoint, nextPoint, prevTangent["outgoing"]); } if ( i==0 && !IsDefined( startVel ) ) { prevTangent = cspline_calcTangentNatural( prevPoint, nextPoint, nextTangent["incoming"] ); } if ( capSpeed ) { csPath.Segments[i] = csplineSeg_calcCoeffsCapSpeed( prevPoint, nextPoint, prevTangent["outgoing"], nextTangent["incoming"], thisSegLength ); path_length += csPath.Segments[i].endAt; } else { csPath.Segments[i] = csplineSeg_calcCoeffs( prevPoint, nextPoint, prevTangent["outgoing"], nextTangent["incoming"] ); path_length += thisSegLength; } csPath.Segments[i].endAt = path_length; // We keep the speed separate from the tangents because otherwise a low speed causes pinching in // the path (which can be achieved with the "tension" parameter in a TCB tangent if you really want it). if ( useNodeSpeeds ) { pathTime = 0; prevEndAt = 0; for ( i = 0; i < csPath.Segments.size; i++ ) { if ( !IsDefined( nodes[i+1].speed ) ) nodes[i+1].speed = nodes[i].speed; thisSegLength = csPath.Segments[i].endAt - prevEndAt; segTime = 2 * thisSegLength / ( ( nodes[i].speed + nodes[i+1].speed ) / 20 ); // /20 to convert from per second to per frame. pathTime += segTime; csPath.Segments[i].endTime = pathTime; prevEndAt = csPath.Segments[i].endAt; csPath.Segments[i].speedStart = nodes[i ].speed / 20; csPath.Segments[i].speedEnd = nodes[i+1].speed / 20; } } else { for ( i = 0; i < csPath.Segments.size; i++ ) { csPath.Segments[i].endTime = csPath.Segments[i].endAt; csPath.Segments[i].speedStart = 1; csPath.Segments[i].speedEnd = 1; } } //prof_end("cspline_makePath"); return csPath; } /* ============= ///ScriptDocBegin "Name: cspline_moveFirstPoint( , , )" "Summary: Moves the start point of the first segment of a path." "Module: CSplines" "CallOn: nothing" "MandatoryArg: : A cubic Hermite spline path as returned from cspline_makePath()" "MandatoryArg: : New position for the beginning of the path" "MandatoryArg: : New velocity (or tangent) for the beginning of the path. Currently mandatory because I haven't had a need to make it optional." "Example: newPath = cspline_moveFirstPoint( nextPath, currentPos, currentVel );" "SPMP: both" ///ScriptDocEnd ============= */ cspline_moveFirstPoint(csPath, newStartPos, newStartVel) { newPath = SpawnStruct(); newPath.Segments = []; posVel = csplineSeg_getPoint( csPath.Segments[0], 1 ); segLength3D = posVel["pos"] - newStartPos; segLength = Length(segLength3D); newPath.Segments[0] = csplineSeg_calcCoeffs( newStartPos, posVel["pos"], newStartVel * segLength, posVel["vel"] ); newPath.Segments[0].endTime = csPath.Segments[0].endTime * segLength / csPath.Segments[0].endAt; newPath.Segments[0].endAt = segLength; lengthDiff = segLength - csPath.Segments[0].endAt; timeDiff = newPath.Segments[0].endTime - csPath.Segments[0].endTime; for ( seg=1; seg , )" "Summary: Take a cubic Hermite spline path and a distance along that path, and returns a position vector and velocity vector." "Module: CSplines" "CallOn: nothing" "MandatoryArg: : A cubic Hermite spline path as returned from cspline_makePath()" "MandatoryArg: : Distance along the path, in inches. The length of the path csPath can be found by cspline_length()" "Example: testModel.origin = cspline_getPointAtDistance( csPath, distance );" "SPMP: both" ///ScriptDocEnd ============= */ cspline_getPointAtDistance(csPath, distance, speedIsImportant) { if (distance <= 0) { segLength = csPath.Segments[0].endAt; posVel = csplineSeg_getPoint( csPath.Segments[0], 0, segLength, csPath.Segments[0].speedStart ); return posVel; } else if (distance >= csPath.Segments[csPath.Segments.size-1].endAt) { if ( csPath.Segments.size > 1 ) segLength = csPath.Segments[csPath.Segments.size-1].endAt - csPath.Segments[csPath.Segments.size-2].endAt; else segLength = csPath.Segments[csPath.Segments.size-1].endAt; posVel = csplineSeg_getPoint( csPath.Segments[csPath.Segments.size-1], 1, segLength, csPath.Segments[csPath.Segments.size-1].speedEnd ); return posVel; } else { // Find the segment we want (brute force way). segNum=0; while (csPath.Segments[segNum].endAt < distance) { segNum++; } if (segNum>0) { startAt = csPath.Segments[segNum-1].endAt; } else { startAt = 0; } segLength = csPath.Segments[segNum].endAt - startAt; normalized = ( distance - startAt ) / segLength; speed = undefined; if ( IsDefined( speedIsImportant ) && speedIsImportant ) speed = cspline_speedFromDistance( csPath.Segments[segNum].speedStart, csPath.Segments[segNum].speedEnd, normalized ); posVel = csplineSeg_getPoint( csPath.Segments[segNum], normalized, segLength, speed ); return posVel; } } /* ============= ///ScriptDocBegin "Name: cspline_getPointAtTime( ,