2024-12-11 11:28:08 +01:00

1226 lines
46 KiB
Plaintext

/* ====== 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<numDimensions; i++)
{
incoming[i] = ( -3*P1[i] + 3*P2[i] - R1[i] ) / 2;
outgoing[i] = incoming[i]; // Make them both the same, assume only the relevant one will be used.
}
}
else
{
// No supplied tangent, therefore just point the new tangent along a straight line between the points.
for (i=0; i<numDimensions; i++)
{
incoming[i] = P2[i] - P1[i];
outgoing[i] = P2[i] - P1[i]; // Make them both the same, assume only the relevant one will be used.
}
}
R = [];
R["incoming"] = ( incoming[0], incoming[1], incoming[2] );
R["outgoing"] = ( outgoing[0], outgoing[1], outgoing[2] );
return R;
}
/* ====== csplineSeg_calcCoeffs ======
* Given points and tangents at either end, returns the coefficients for a cubic spline segment. */
csplineSeg_calcCoeffs(P1, P2, R1, R2)
{
numDimensions = 3;
segVars = SpawnStruct();
segVars.n3 = [];
segVars.n2 = [];
segVars.n = [];
segVars.c = [];
for (i=0; i<numDimensions; i++)
{
segVars.n3[i] = 2*P1[i] - 2*P2[i] + R1[i] + R2[i] ;
segVars.n2[i] = -3*P1[i] + 3*P2[i] - 2*R1[i] - R2[i] ;
segVars.n[i] = R1[i] ;
segVars.c[i] = P1[i] ;
}
return segVars;
}
/* ====== csplineSeg_calcCoeffs ======
* Given points and tangents at either end, returns the coefficients for a cubic spline segment.
* One problem with cubic spline paths is that the speed has no upper bound and commonly goes to 1.25 times the speed you want it to.
* This function assumes the speed at the tangents is <= 1, and recalculates the segLength so that the speed in between doesn't exceed 1. */
csplineSeg_calcCoeffsCapSpeed(P1, P2, R1, R2, segLength)
{
csSeg = csplineSeg_calcCoeffs( P1, P2, R1, R2 );
//prof_begin("csplines_topspeed");
topSpeed = csplineSeg_calcTopSpeed( csSeg, segLength );
if ( topSpeed > 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( <csplineSeg> , <x>, <segLength>, <speedMult> )"
"Summary: Given a Hermite Spline segment and a normalized distance along the segment, return position, velocity and acceleration vectors."
"Module: CSplines"
"CallOn: nothing"
"MandatoryArg: <csplineSeg>: a cubic Hermite spline segment - a struct containing n3, n2, n and c, each of which is an array of 3 floats."
"MandatoryArg: <x>: The distance along the segment. 0 <= x <= 1."
"OptionalArg: <segLength>: The length of this segment, used for calculating the correct velocity and acceleration."
"OptionalArg: <speedMult>: 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<numDimensions; i++)
{
posArray[i] = (csplineSeg.n3[i]*x*x*x) + (csplineSeg.n2[i]*x*x) + (csplineSeg.n[i]*x) + csplineSeg.c[i];
velArray[i] = (3*csplineSeg.n3[i]*x*x) + (2*csplineSeg.n2[i]*x) + csplineSeg.n[i];
accArray[i] = (6*csplineSeg.n3[i]*x) + (2*csplineSeg.n2[i]);
}
returnArray["pos"] = ( posArray[0], posArray[1], posArray[2] );
returnArray["vel"] = ( velArray[0], velArray[1], velArray[2] );
returnArray["acc"] = ( accArray[0], accArray[1], accArray[2] );
if (IsDefined( segLength ) )
{
returnArray["vel"] /= segLength;
returnArray["acc"] /= segLength * segLength;
}
if (IsDefined( speedMult ) )
{
returnArray["vel"] *= speedMult;
returnArray["acc"] *= speedMult * speedMult;
}
returnArray[ "speed" ] = speedMult;
return returnArray;
}
// csplineSeg_calcTopSpeed
// Find the top speed of a cubic spline segment. There are two ways I could find to do this. One using derivatives, which
// still requires some numerical iteration at the end because I can't do a cube root, and one that just iterates along the
// curve. In my test map, the derivatives version took 3.6-7ms to calculate 75 segements, while the iterating version took
// 110ms at a 5 inch step length, 31ms at 5 samples per segment.
csplineSeg_calcTopSpeed( csplineSeg, segLength )
{
// prof_begin("cspline_ts_derive");
v1 = csplineSeg_calcTopSpeedByDeriving( csplineSeg, segLength );
// prof_end("cspline_ts_derive");
// prof_begin("cspline_ts_iterate");
// v2 = csplineSeg_calcTopSpeedByStepping( csplineSeg, 5, segLength );// For 5 inch steps, I used Int(segLength/5)+1
// prof_end("cspline_ts_iterate");
//IPrintLn ( "segLength: "+segLength+", v1: "+v1+", v2: "+v2 );
return v1;
}
csplineSeg_calcTopSpeedByDeriving( csplineSeg, segLength )
{
// Total speed (squared) = speed[0]^2 + speed[1]^2 + speed[2]^2
// speed[0] = 3*n3[0]*x^2 + 2*n2[0]*x + n[0]
// Speed[0]^2 = 9*n3[0]^2*x^4 + 12*n3[0]*n2[0]*x^3 + ( 6*n3[0]*n + 4*n2[0]^2 )*x^2 + 4*n2[0]*n[0]*x + n[0]^2
// I can sum these by summing the parameters, eg n3_n2 = (n3[0]*n2[0]) + (n3[1]*n2[1]) + (n3[2]*n2[2])
n3_n3 = 0; n3_n2 = 0; n3_n = 0; n2_n2 = 0; n2_n = 0; n_n = 0;
for (axis=0; axis<3; axis++)
{
n3_n3 += csplineSeg.n3[axis] * csplineSeg.n3[axis];
n3_n2 += csplineSeg.n3[axis] * csplineSeg.n2[axis];
n3_n += csplineSeg.n3[axis] * csplineSeg.n[axis];
n2_n2 += csplineSeg.n2[axis] * csplineSeg.n2[axis];
n2_n += csplineSeg.n2[axis] * csplineSeg.n[axis];
n_n += csplineSeg.n[axis] * csplineSeg.n[axis];
}
// So the total speed (squared) is represented by a quartic function, with coefficients:
/*
a = 9*n3_n3;
b = 12*n3_n2;
c = 6*n3_n + 4*n2_n2;
d = 4*n2_n;
e = n_n;
*/
// I have a quartic equation to find the maximum of. Derivative is cubic:
// 36*n3_n3*x^3 + 36*n3_n2*x^2 + ( 12*n3_n + 8*n2_n2 )*x + 4*n2_n
// So
a = 36*n3_n3;
b = 36*n3_n2;
c = 12*n3_n + 8*n2_n2;
d = 4*n2_n;
// So I need to find the roots of the cubic, and I only care about the ones where the slope of the cubic is negative.
// I can't do it mathematically without a cube root function, so do it numerically.
// I need to find out exactly how many there are so I can be sure I don't miss any.
// Also, by restricting myself to the 0-1 interval, I can pretty easily find a bunch of early outs.
values = [];
values[0] = 0;
if ( a == 0 )
{
if ( ( b == 0 ) && ( c == 0 ) && ( d == 0 ) ) {
// The original quartic was a flat line with constant value n_n. That is, the spline is a straight line with no variation.
return sqrt( n_n ) / segLength;
}
// a==0, so it's really a quadratic so it's easy to get its roots.
cubicRoots = maps\interactive_models\_interactive_utility::rootsOfQuadratic( b, c, d );
if ( IsDefined( cubicRoots[0] ) && cubicRoots[0] > 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<points.size; i++ )
{
x0 = points[i-1];
x1 = points[i];
startVal = ( a*x0*x0*x0 ) + ( b*x0*x0 ) + ( c*x0 ) + d;
endVal = ( a*x1*x1*x1 ) + ( b*x1*x1 ) + ( c*x1 ) + d;
if ( (startVal > 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( <targetname> )"
"Summary: Take a targetname for the first node in a path and return an array of nodes."
"Module: CSplines"
"CallOn: nothing"
"MandatoryArg: <targetname>: 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( <startOrg>, <endOrg>, <startVel>, <endVel> )"
"Summary: Take two points and returns a data structure with a single-piece hermite spline path defined in it.
"Module: CSplines"
"CallOn: nothing"
"MandatoryArg: <startOrg>: Point to start from."
"MandatoryArg: <endOrg>: Point to end at."
"OptionalArg: <startVel>: Velocity vector for the beginning of the spline; indicates movement in one frame. Will use natural (ie zero acceleration) if not supplied."
"OptionalArg: <endVel>: 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( <startOrg>, <endOrg>, <startVel>, <endVel> )"
"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: <startOrg>: Point to start from."
"MandatoryArg: <endOrg>: Point to end at."
"OptionalArg: <startVel>: Velocity vector for the beginning of the spline; indicates movement in one frame. Will use natural (ie zero acceleration) if not supplied."
"OptionalArg: <endVel>: Velocity vector for the end of the spline; indicates movement in one frame. Will use natural (ie zero acceleration) if not supplied."
"OptionalArg: <forceCreateIntermediateNodes>: 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<nodes.size; n++ )
thread draw_line_for_time ( nodes[n-1].origin, nodes[n].origin, 0, .7, .7, 1 );
}
#/
return cspline_makePath( nodes, true, dirs[0], dirs[1] );
}
/*
=============
///ScriptDocBegin
"Name: cspline_makePath( <path_nodes>, <startVel>, <endVel> )"
"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: <path_nodes>: An array of nodes (or any structs with .origin fields)."
"OptionalArg: <useNodeSpeeds>: Use the speed keypairs from the nodes."
"OptionalArg: <startVel>: Velocity (or tangent) vector for the beginning of the spline. Will use natural (ie zero acceleration) if not supplied."
"OptionalArg: <endVel>: Velocity (or tangent) vector for the end of the spline. Will use natural (ie zero acceleration) if not supplied."
"OptionalArg: <capSpeed>: 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( <csPath> , <newStartPos>, <newStartVel> )"
"Summary: Moves the start point of the first segment of a path."
"Module: CSplines"
"CallOn: nothing"
"MandatoryArg: <csPath>: A cubic Hermite spline path as returned from cspline_makePath()"
"MandatoryArg: <newStartPos>: New position for the beginning of the path"
"MandatoryArg: <newStartVel>: 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<csPath.Segments.size; seg++ )
{
newPath.Segments[seg] = csplineSeg_copy( csPath.Segments[seg] );
newPath.Segments[seg].endAt += lengthDiff;
newPath.Segments[seg].endTime += timeDiff;
}
return newPath;
}
/*
=============
///ScriptDocBegin
"Name: cspline_getPointAtDistance( <csPath> , <distance> )"
"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: <csPath>: A cubic Hermite spline path as returned from cspline_makePath()"
"MandatoryArg: <distance>: 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( <csPath> , <time> )"
"Summary: Take a cubic Hermite spline path and a time along that path, and returns a position vector and velocity vector. Useful if you set the path up with varying speed at each node."
"Module: Entity"
"CallOn: An entity"
"MandatoryArg: <csPath>: A cubic Hermite spline path as returned from cspline_makePath()"
"MandatoryArg: <time>: Time spent traveling along the path, in whatever units you set the path up with (usually frames). The total time for the path can be found by cspline_time()"
"Example: posVel = cspline_getPointAtTime( self.path, piece.distance );"
"SPMP: singleplayer"
///ScriptDocEnd
=============
*/
cspline_getPointAtTime( csPath, time )
{
if (time <= 0)
{
segLength = csPath.Segments[0].endAt;
posVel = csplineSeg_getPoint( csPath.Segments[0], 0, segLength, csPath.Segments[0].speedStart );
return posVel;
}
else if (time >= csPath.Segments[csPath.Segments.size-1].endTime)
{
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].endTime < time)
{
segNum++;
}
if (segNum>0) {
startTime = csPath.Segments[segNum-1].endTime;
segLength = csPath.Segments[segNum].endAt - csPath.Segments[segNum-1].endAt;
} else {
startTime = 0;
segLength = csPath.Segments[0].endAt;
}
// Convert the time to a distance and get the point.
segTime = csPath.Segments[segNum].endTime - startTime;
normTime = ( time - startTime ) / segTime;
speed = csPath.Segments[segNum].speedStart + ( normTime * ( csPath.Segments[segNum].speedEnd - csPath.Segments[segNum].speedStart ) );
dist = ( time - startTime ) * ( csPath.Segments[segNum].speedStart + speed ) / 2;
normDist = dist / segLength;
posVel = csplineSeg_getPoint( csPath.Segments[segNum], normDist, segLength, speed );
return posVel;
}
}
cspline_speedFromDistance( speedStart, speedEnd, normalizedDistance )
{
// Let's say sS = speedStart, sD = speedEnd, t = normalized time, s = current speed
// We're pretending that the distance from start to end is 1.
// Thus total time = 1 / total average speed = 2/(sS+sD)
// acceleration a = speed change / total time = (sD-sS) * (sS+sD) / 2
// Speed s = sS + a*t
// t = (s-sS)/a
// d = t * average speed
// d = ( (s-sS)/a ) * ( (sS+s)/2 )
// s = sqrt( 2*a*d + sS^2 )
d = normalizedDistance;
a = ( speedEnd - speedStart ) * ( speedEnd + speedStart ) / 2;
return sqrt( ( 2*a*d ) + ( speedStart*speedStart ) );
}
cspline_adjustTime( csPath, newTime )
{
// Leave the first and last node alone, and scale the speeds of all the other nodes to make the path take a different amount of time.
// time = distance / av speed
// R = speed multiply ratio
// d0, d1 and d2 are distances for first, sum of middle and last segments, respectively.
// av speed for middle sections multiplies by R, ie time for middle sections divides by R
// av speed for ends multiplies by (1+R)/2
// tnew = ( (t0+t2)*2/(1+R) ) + t1/R
// R = ( sqrt( ( (2*t0) + t1 + (2*t2) - tnew )^2 + (4*t1*tnew) ) + (2*d0) + d1 + (2*d2) - tnew ) / (2*tnew)
oldTime = cspline_time( csPath );
t0 = csPath.Segments[0].endTime;
t1 = csPath.Segments[ csPath.Segments.size-2 ].endTime - t0;
t2 = csPath.Segments[ csPath.Segments.size-1 ].endTime - csPath.Segments[ csPath.Segments.size-2 ].endTime;
tempsum = (2*t0) + t1 + (2*t2) - newtime;
R = ( sqrt( (tempsum*tempsum) + (4*t1*newTime) ) + tempsum ) / (2*newTime);
/#
checkNewTime = ( (t0+t2)*2/(1+R) ) + (t1/R);
AssertEx( abs(checkNewTime - newTime) < 0.001, "cspline_adjustTime math failure: "+checkNewTime+" != "+newTime );
#/
nextTime = undefined; // For debugging only.
checkEndTime = undefined; // For debugging only.
csPath.Segments[0].speedEnd *= R;
offset = csPath.Segments[0].endtime * ( (1/R) - (2/(1+R)) );
/#
checkEndTime = ( csPath.Segments[0].endtime / R ) - offset;
nextTime = csPath.Segments[1].endtime - csPath.Segments[0].endtime;
#/
csPath.Segments[0].endtime /= (1+R)/2;
AssertEx( abs( checkEndTime - csPath.Segments[0].endtime ) < 0.001, "cspline_adjustTime math failure (offset). "+checkEndTime+" != "+csPath.Segments[0].endtime );
for ( i=1; i<csPath.Segments.size-1; i++ )
{
thisOldTime = undefined; // For debugging only.
/#
thisOldTime = nextTime;
nextTime = csPath.Segments[i+1].endtime - csPath.Segments[i].endtime;
#/
csPath.Segments[i].speedStart *= R;
csPath.Segments[i].speedEnd *= R;
csPath.Segments[i].endtime /= R;
csPath.Segments[i].endtime -= offset;
/#
thisTime = csPath.Segments[i].endtime - csPath.Segments[i-1].endtime;
AssertEx( abs(thisTime - (thisOldTime/R) ) < 0.001, "cspline_adjustTime math failure. "+thisTime+" != "+(thisOldTime/R) );
#/
}
i = csPath.Segments.size-1;
csPath.Segments[i].speedStart *= R;
csPath.Segments[i].endtime = newTime;
/#
t0New = csPath.Segments[0].endtime;
t1new = csPath.Segments[csPath.Segments.size-2].endtime - csPath.Segments[0].endtime;
t2New = csPath.Segments[i].endtime - csPath.Segments[i-1].endtime;
AssertEx( abs( t0New - (t0*2/(1+R)) ) < 0.001, "cspline_adjustTime math failure t0. "+t0New+" != "+(t0*2/(1+R)) );
AssertEx( abs( t1new - (t1/R) ) < 0.001, "cspline_adjustTime math failure t1. "+t1new+" != "+(t1/R) );
AssertEx( abs( t2New - (t2*2/(1+R)) ) < 0.001, "cspline_adjustTime math failure t2. "+t2New+" != "+(t2*2/(1+R)) );
#/
}
/*
=============
///ScriptDocBegin
"Name: cspline_makeNoisePath( <size> , <minVal> , <maxVal> )"
"Summary: Creates a looping cubic spline path of random 3D points."
"Module: CSplines"
"CallOn: nothing"
"MandatoryArg: <size>: Number of entries in the returned array"
"MandatoryArg: <minVal>: Minimum allowed for the random values. Must be a vector."
"MandatoryArg: <maxVal>: Maximum allowed for the random values. Must be a vector."
"OptionalArg: <firstVal>: Value for the first and last point in the path."
"Example: nodes = cspline_makeNoisePathNodes( 10, -5, 5 );"
"SPMP: both"
///ScriptDocEnd
=============
*/
cspline_makeNoisePath( size, minVal, maxVal, firstVal )
{
nodes = cspline_makeNoisePathNodes( size, minVal, maxVal );
if ( IsDefined(firstVal) ) {
nodes[1].origin = firstVal; // Nodes[1] will eventually be the start and end point of the path.
}
// Duplicate the first three onto the end so the eventual path loops with no discontinuities
newNode = SpawnStruct();
newNode.origin = nodes[0].origin;
nodes[ nodes.size ] = newNode;
newNode = SpawnStruct();
newNode.origin = nodes[1].origin;
nodes[ nodes.size ] = newNode;
newNode = SpawnStruct();
newNode.origin = nodes[2].origin;
nodes[ nodes.size ] = newNode;
cspath = cspline_makePath( nodes );
// Now strip the first and last off
newPath = SpawnStruct();
newPath.Segments = [];
for ( seg = 0; seg < csPath.Segments.size - 2; seg++ )
{
newPath.Segments[ seg ] = csplineSeg_copy( csPath.Segments[ seg + 1 ] );
newPath.Segments[ seg ].endAt = seg + 1;
}
return newPath;
}
cspline_makeNoisePathNodes( size, minVal, maxVal )
{
AssertEx( minVal[0]<maxVal[0] && minVal[1]<maxVal[1] && minVal[2]<maxVal[2], "minVal must be < maxVal: "+minVal+", "+maxVal );
nodes = [];
for ( i = 0; i < size; i++ )
{
nodes[ i ] = SpawnStruct();
x = RandomFloatRange( minVal[ 0 ], maxVal[ 0 ] );
y = RandomFloatRange( minVal[ 1 ], maxVal[ 1 ] );
z = RandomFloatRange( minVal[ 2 ], maxVal[ 2 ] );
nodes[ i ].origin = ( x, y, z );
}
return nodes;
}
/*
=============
///ScriptDocBegin
"Name: cspline_test( <csPath> , <timeSecs> )"
"Summary: Displays a cubic spline path in 3D, if the dvar interactives_debug is set."
"Module: CSplines"
"CallOn: nothing"
"MandatoryArg: <csPath>: A cubic spline path, as returned from cspline_makePath"
"OptionalArg: <timeSecs>: Time in seconds to display the path for. Defaults to infinity."
"Example: thread cspline_test( "bird_path" );"
"SPMP: both"
///ScriptDocEnd
=============
*/
cspline_test( csPath, timeSecs )
{
/#
sec = 0;
arrowLength = 10;
arrowSpacing = 50;
maxArrows = 50;
for (;;)
{
if ( GetDvarInt( "interactives_debug" ) )
{
pathLength = cspline_time( csPath );
minArrowSpacing = pathLength / maxArrows;
col = ( minArrowSpacing/arrowSpacing, arrowSpacing/minArrowSpacing, 0 ); // Fade to red if arrows are widely spaced
if ( minArrowSpacing > arrowSpacing ) arrowSpacing = minArrowSpacing;
posVel = cspline_getPointAtTime( csPath, 0 );
for(time = 0; time <= cspline_time( csPath ); time += arrowSpacing)
{
prevPos = posVel["pos"];
if ( isDefined( csPath.Segments[0].speedEnd ) )
posVel = cspline_getPointAtTime( csPath, time );
else
posVel = cspline_getPointAtDistance( csPath, time );
thread draw_arrow_time( prevPos, posVel["pos"], (0,1,0), 1 );
}
hsArray = cspline_getNodes( csPath );
size=12;
foreach ( i in [0, hsArray.size-1] )
{
thread draw_line_for_time ( hsArray[ i ][ "pos" ] -( size, 0, 0 ), hsArray[ i ][ "pos" ] +( size, 0, 0 ), 1 , 0 , 0 , 1 );
thread draw_line_for_time ( hsArray[ i ][ "pos" ] -( 0, size, 0 ), hsArray[ i ][ "pos" ] +( 0, size, 0 ), 1 , 0 , 0 , 1 );
thread draw_line_for_time ( hsArray[ i ][ "pos" ] -( 0, 0, size ), hsArray[ i ][ "pos" ] +( 0, 0, size ), 1 , 0 , 0 , 1 );
//Print3d( hsArray[ i ][ "pos" ] -( 0, 0, size ) , ( "accn: " + ( 100 * hsArray[ i ][ "acc" ] ) ) , ( 1, 0, 0 ), 1, 1, 20 );
/*Print3d( hsArray[ i ][ "pos" ] -( 0, 0, size ), ( "time: " + ( hsArray[ i ][ "time" ] ) ) , ( 1, 1, 0 ), 1, 1, 20 );
speed = Length( hsArray[ i ][ "vel" ] );
Print3d( hsArray[ i ][ "pos" ] -( 0, 0, 2*size ), ( "speed: " + speed ) , ( 1, 1, 0 ), 1, 1, 20 );
if ( i==0 && IsDefined( csPath.Segments[i].straightLineLength ) )
{
Print3d( hsArray[ i ][ "pos" ] -( 0, 0, 3*size ), ( "straight length: " + csPath.Segments[i].straightLineLength ) , ( 1, 1, 0 ), 1, 1, 20 );
Print3d( hsArray[ i ][ "pos" ] -( 0, 0, 4*size ), ( "top speed: " + csPath.Segments[i].calcTopSpeed ) , ( 1, 1, 0 ), 1, 1, 20 );
Print3d( hsArray[ i ][ "pos" ] -( 0, 0, 5*size ), ( "actual length: " + csPath.Segments[i].actualLength ) , ( 1, 1, 0 ), 1, 1, 20 );
}*/
}
for ( i = 1; i < hsArray.size - 1; i++ )
{
thread draw_line_for_time ( hsArray[ i ][ "pos" ] -( size, 0, 0 ), hsArray[ i ][ "pos" ] +( size, 0, 0 ), 1 , 1 , 0 , 1 );
thread draw_line_for_time ( hsArray[ i ][ "pos" ] -( 0, size, 0 ), hsArray[ i ][ "pos" ] +( 0, size, 0 ), 1 , 1 , 0 , 1 );
thread draw_line_for_time ( hsArray[ i ][ "pos" ] -( 0, 0, size ), hsArray[ i ][ "pos" ] +( 0, 0, size ), 1 , 1 , 0 , 1 );
//Print3d( hsArray[ i ][ "pos" ] -( 0, 0, size ) , ( "accn in: " + ( 100 * hsArray[ i ][ "acc" ] ) ) , ( 1, 1, 0 ), 1, 1, 20 );
//Print3d( hsArray[ i ][ "pos" ] -( 0, 0, size + 10 ), ( "accn out: " + ( 100 * hsArray[ i ][ "acc_out" ] ) ) , ( 1, 1, 0 ), 1, 1, 20 );
/*Print3d( hsArray[ i ][ "pos" ] -( 0, 0, size ), ( "time: " + ( hsArray[ i ][ "time" ] ) ) , ( 1, 1, 0 ), 1, 1, 20 );
speed = Length( hsArray[ i ][ "vel" ] );
Print3d( hsArray[ i ][ "pos" ] -( 0, 0, 2*size ), ( "speed: " + speed ) , ( 1, 1, 0 ), 1, 1, 20 );
if ( IsDefined( csPath.Segments[i].straightLineLength ) )
{
Print3d( hsArray[ i ][ "pos" ] -( 0, 0, 3*size ), ( "straight length: " + csPath.Segments[i].straightLineLength ) , ( 1, 1, 0 ), 1, 1, 20 );
Print3d( hsArray[ i ][ "pos" ] -( 0, 0, 4*size ), ( "top speed: " + csPath.Segments[i].calcTopSpeed ) , ( 1, 1, 0 ), 1, 1, 20 );
Print3d( hsArray[ i ][ "pos" ] -( 0, 0, 5*size ), ( "actual length: " + csPath.Segments[i].actualLength ) , ( 1, 1, 0 ), 1, 1, 20 );
}*/
}
}
wait 1;
sec++;
if ( IsDefined( timeSecs) && ( sec >= timeSecs ) ) break;
}
#/
}
/*
=============
///ScriptDocBegin
"Name: cspline_testNodes( <nodes> , <timeSecs> )"
"Summary: Displays the nodes with lines between them."
"Module: CSplines"
"CallOn: nothing"
"MandatoryArg: <nodes>: An array of structs with .origin fields"
"MandatoryArg: <timeSecs>: Time in seconds to display the path for"
"Example: thread cspline_testNodes( nodes_array, 5 );"
"SPMP: both"
///ScriptDocEnd
=============
*/
cspline_testNodes( nodes, timeSecs )
{
size = 20;
prevNode = undefined;
foreach ( node in nodes )
{
if ( IsDefined( prevNode ) )
thread draw_arrow_time( prevNode.origin, node.origin, (0,1,0), timeSecs );
prevNode = node;
}
foreach ( node in nodes )
{
thread draw_line_for_time ( node.origin -( size, 0, 0 ), node.origin +( size, 0, 0 ), 1 , 1 , 0 , timeSecs );
thread draw_line_for_time ( node.origin -( 0, size, 0 ), node.origin +( 0, size, 0 ), 1 , 1 , 0 , timeSecs );
thread draw_line_for_time ( node.origin -( 0, 0, size ), node.origin +( 0, 0, size ), 1 , 1 , 0 , timeSecs );
}
}
csplineSeg_copy (csSeg )
{
newSeg = SpawnStruct();
numDimensions = 3;
for (d=0; d<numDimensions; d++)
{
newSeg.n3[d] = csSeg.n3[d];
newSeg.n2[d] = csSeg.n2[d];
newSeg.n[d] = csSeg.n[d];
newSeg.c[d] = csSeg.c[d];
}
newSeg.endAt = csSeg.endAt;
newSeg.endTime = csSeg.endTime;
return newSeg;
}
// Simple, readable way of getting the length of a path
cspline_length( csPath )
{
return csPath.Segments[ csPath.Segments.size - 1 ].endAt;
}
cspline_time( csPath )
{
return csPath.Segments[ csPath.Segments.size - 1 ].endTime;
}
//-----------------------------------------------------------
//
// Cubic spline noise
//
//-----------------------------------------------------------
// cspline_InitNoise
//
/*
=============
///ScriptDocBegin
"Name: cspline_InitNoise( <center> , <startPoint> , <variance_amt> , <variance_time> )"
"Summary: Creates a series of points with random positions around a center point. Returns a struct that can be passed to cspline_Noise to get a position."
"Module: CSplines"
"CallOn: nothing"
"MandatoryArg: <center>: Center point for the returned positions"
"MandatoryArg: <variance_amt>: Max distance positions can be from center."
"MandatoryArg: <variance_time>: "
"OptionalArg: <startPoint>: Position for first point."
"Example: "
"SPMP: singleplayer"
///ScriptDocEnd
=============
*/
cspline_InitNoise( center, variance_amt, variance_time, startPoint )
{
ns = SpawnStruct();
largeAmt = variance_amt;
ns.largeStep = variance_time;
//smallAmt = small_variance_amt;
//ns.smallStep = small_variance_time;
centerMin = ( center[ 0 ] - largeAmt, center[ 1 ] - largeAmt, center[ 2 ] - largeAmt );
centerMax = ( center[ 0 ] + largeAmt, center[ 1 ] + largeAmt, center[ 2 ] + largeAmt );
startPoint = ( center[ 0 ], center[ 1 ], center[ 2 ] - largeAmt );
ns.largeScale = cspline_makeNoisePath( 10, centerMin, centerMax, startPoint );
//ns.smallScale = cspline_makeNoisePath( 10, -1 * ( smallAmt, smallAmt, smallAmt ), ( smallAmt, smallAmt, smallAmt ) );
ns.largeScale.Length = ns.largeScale.Segments[ ns.largeScale.Segments.size - 1 ].endAt;
//ns.smallScale.Length = ns.smallScale.Segments[ ns.smallScale.Segments.size - 1 ].endAt;
thread cspline_test( ns.largeScale, 20 );
return ns;
}
/*
=============
///ScriptDocBegin
"Name: cspline_Noise( <ns> , <frameNum> )"
"Summary: "
"Module: Entity"
"CallOn: An entity"
"MandatoryArg: <ns>: "
"MandatoryArg: <frameNum>: "
"Example: "
"SPMP: singleplayer"
///ScriptDocEnd
=============
*/
cspline_Noise( ns, frameNum )
{
tl = mod( frameNum/ns.largeStep, ns.largeScale.length );
//ts = mod( frameNum/ns.smallStep, ns.smallScale.length );
pl = cspline_getPointAtDistance( ns.largeScale, tl );
//ps = cspline_getPointAtDistance( ns.smallScale, ts );
return pl["pos"];// + ps["pos"];
}