| 1 |
<?xml version="1.0" encoding="UTF-8"?>
|
| 2 |
<!DOCTYPE X3D PUBLIC "ISO//Web3D//DTD X3D 3.2//EN" "https://www.web3d.org/specifications/x3d-3.2.dtd">
|
| 3 | <X3D profile='Immersive' version='3.2' xmlns:xsd='http://www.w3.org/2001/XMLSchema-instance' xsd:noNamespaceSchemaLocation='https://www.web3d.org/specifications/x3d-3.2.xsd'> |
| 4 | <head> |
| 5 | <meta name='title' content=' WaypointInterpolatorPrototype.x3d '/> |
| 6 | <meta name='description' content='Prototype to provide a set of waypoints, plus either leg durations or speed, and return position/orientation interpolation values. Included example can be stopped/started via TouchSensor mouse over floor Box.'/> |
| 7 | <meta name='creator' content='Don Brutzman, Curtis Blais, Jeff Weekley, Jane Wu'/> |
| 8 | <meta name='created' content='6 April 2001'/> |
| 9 | <meta name='modified' content='23 August 2023'/> |
| 10 | <meta name='identifier' content=' https://www.web3d.org/x3d/content/examples/Savage/Tools/Animation/WaypointInterpolatorPrototype.x3d '/> |
| 11 | <meta name='reference' content=' https://www.web3d.org/x3d/content/examples/Savage/Tools/Animation/WaypointInterpolatorExample.x3d '/> |
| 12 | <meta name=' warning ' content=' browsers do not compute pitch angle consistently '/> |
| 13 | <meta name='generator' content='X3D-Edit 4.0, https://www.web3d.org/x3d/tools/X3D-Edit'/> |
| 14 | <meta name='license' content='../../license.html'/> |
| 15 | </head> |
| 16 | <Scene> |
| 17 | <WorldInfo title='WaypointInterpolatorPrototype.x3d'/> |
| 18 | <ProtoDeclare name='WaypointInterpolator' appinfo='Reads waypoints and legSpeeds/legDurations/defaultSpeed to provide a customizable position/orientation interpolator.'> |
| 19 | <ProtoInterface> |
| 20 |
<field name='description' type='SFString' accessType='initializeOnly'
appinfo='Short description of what is animated by this WaypointInterpolator.'/> |
| 21 |
<field name='waypoints' type='MFVec3f' value='0 0 0 0 0 0' accessType='initializeOnly'
appinfo='Waypoints being traversed with interpolation of intermediate positions and orientations.'/> |
| 22 |
<field name='add_waypoint' type='SFVec3f' accessType='inputOnly'
appinfo='Add another single waypoint to array of waypoints recalculate interpolator values.'/> |
| 23 |
<field name='set_waypoints' type='MFVec3f' accessType='inputOnly'
appinfo='Replace all waypoints recalculate interpolator values.'/> |
| 24 |
<field name='pitchUpDownForVerticalWaypoints' type='SFBool' value='false' accessType='initializeOnly'
appinfo='Whether to pitch child geometry (such as a vehicle) up or down to match vertical slope'/> |
| 25 | <!-- Priority of use: legSpeeds (m/sec), legDurations (seconds), defaultSpeed (m/sec) --> |
| 26 |
<field name='legSpeeds' type='MFFloat' accessType='initializeOnly'
appinfo='Units m/sec. If used, array lengths for legSpeeds and legDurations must be one less than number of waypoints.'> |
| 27 | <!-- default initialization is empty array [] --> |
| 28 | </field> |
| 29 |
<field name='legDurations' type='MFTime' accessType='initializeOnly'
appinfo='Units in seconds. If used, array lengths for legSpeeds and legDurations must be one less than number of waypoints.'> |
| 30 | <!-- default initialization is empty array [] --> |
| 31 | </field> |
| 32 |
<field name='defaultSpeed' type='SFFloat' value='1' accessType='initializeOnly'
appinfo='Units m/sec.'/> |
| 33 |
<field name='turningRate' type='SFFloat' value='90' accessType='initializeOnly'
appinfo='turningRate (degrees/second) also determines standoff distance prior to waypoint where turn commences. If 0 turns are instantaneous.'/> |
| 34 |
<field name='totalDuration' type='SFTime' accessType='outputOnly'
appinfo='Output calculation summing all leg durations, useful for setting TimeSensor cycleInterval. Units in seconds.'/> |
| 35 | <!-- interpolation fields --> |
| 36 |
<field name='set_fraction' type='SFFloat' accessType='inputOnly'
appinfo='exposed PositionInterpolator and OrientationInterpolator setting'/> |
| 37 |
<field name='position_changed' type='SFVec3f' accessType='outputOnly'
appinfo='exposed PositionInterpolator setting'/> |
| 38 |
<field name='orientation_changed' type='SFRotation' accessType='outputOnly'
appinfo='exposed OrientationInterpolator setting'/> |
| 39 | <!-- display-related fields --> |
| 40 |
<field name='lineColor' type='SFColor' value='0.6 0.6 0.6' accessType='inputOutput'
appinfo='default color for non-active line segments'/> |
| 41 |
<field name='highlightSegmentColor' type='SFColor' value='0.3 0.3 1' accessType='inputOutput'
appinfo='active segment highlight color'/> |
| 42 |
<field name='transparency' type='SFFloat' value='0' accessType='inputOutput'
appinfo='1.0 is completely transparent, 0.0 is completely opaque.'/> |
| 43 |
<field name='labelDisplayMode' type='SFString' value='waypoints' accessType='initializeOnly'
appinfo='allowed values: none; waypoints (produce labels at each waypoint); or interpolation (produce single moving label at interpolator time course speed location)'/> |
| 44 |
<field name='heightLabel' type='SFString' value='altitude' accessType='initializeOnly'
appinfo='allowed values: altitude depth (negate Y value) none'/> |
| 45 |
<field name='labelOffset' type='SFVec3f' value='0 -1 0' accessType='initializeOnly'
appinfo='heightLabel relative location'/> |
| 46 |
<field name='labelFontSize' type='SFFloat' value='1' accessType='initializeOnly'
appinfo='heightLabel text size'/> |
| 47 |
<field name='labelColor' type='SFColor' value='0.8 0.8 0.8' accessType='initializeOnly'
appinfo='heightLabel text color'/> |
| 48 |
<field name='traceEnabled' type='SFBool' value='false' accessType='initializeOnly'
appinfo='enable console output to trace script computations and prototype progress'/> |
| 49 |
<field name='outputInitializationComputations' type='SFBool' value='true' accessType='initializeOnly'
appinfo='Output the number of waypoints totalDistance and totalDuration to console upon initialization'/> |
| 50 |
<field name='verticalDropLineColor' type='SFColor' value='0.4 0.4 0.4' accessType='inputOutput'
appinfo='default color for vertical drop-line segments'/> |
| 51 |
<field name='verticalDropLineTransparency' type='SFFloat' value='1' accessType='inputOutput'
appinfo='1.0 is completely transparent, 0.0 is completely opaque.'/> |
| 52 | </ProtoInterface> |
| 53 | <ProtoBody> |
| 54 | <!-- First node in prototype determines node type of prototype. This prototype extends PositionInterpolator and OrientationInterpolator functionality. Nevertheless, a Group node is wrapped around all of them in order to avoid a Prototype bug in CosmoPlayer. --> |
| 55 | <Group> |
| 56 | <!-- key, keyValue will be generated by WaypointTrackScript. set_fraction is a common input to both interpolators. Interpolator value outputs are returned via the corresponding Prototype field interfaces. --> |
| 57 |
<!-- PositionInterpolator
WaypointPI.instance is a DEF node that has 1 USE node: USE_1
<!-- ROUTE information for WaypointPI.instance node: [from WaypointTrackScript.finalPositionKey to key ] [from WaypointTrackScript.finalPositionKeyValueArray to keyValue ] [from value_changed to MovingVehicleLabel.translation ] --> <PositionInterpolator DEF='WaypointPI.instance' key='0 0.5 1' keyValue='0 0 0 1 1 1 2 2 2'> |
| 58 | <IS> |
| 59 | <connect nodeField='set_fraction' protoField='set_fraction'/> |
| 60 | <connect nodeField='value_changed' protoField='position_changed'/> |
| 61 | </IS> |
| 62 | </PositionInterpolator> |
| 63 |
<!-- OrientationInterpolator
WaypointOI.instance is a DEF node that has 1 USE node: USE_1
<!-- ROUTE information for WaypointOI.instance node: [from value_changed to MovingVehicleLabel.rotation ] --> <OrientationInterpolator DEF='WaypointOI.instance'> |
| 64 | <IS> |
| 65 | <connect nodeField='set_fraction' protoField='set_fraction'/> |
| 66 | <connect nodeField='value_changed' protoField='orientation_changed'/> |
| 67 | </IS> |
| 68 | </OrientationInterpolator> |
| 69 |
<!-- Group
CoordinateLabelsAndViewpointsGroup is a DEF node that has 1 USE node: USE_1 --> <Group DEF='CoordinateLabelsAndViewpointsGroup'/> |
| 70 |
<!-- ROUTE information for WaypointTrackScript node:
[from finalPositionKey to WaypointPI.instance.key
]
[from finalPositionKeyValueArray to WaypointPI.instance.keyValue
]
[from verticalDropLineIndices to VerticalDropLine.set_coordIndex
]
[from verticalDropLinePoints to VerticalDropLineCoordinates.point
]
[from highlightCoordinates to HighlightSegmentCoordinates.point
]
[from pointIndices to WaypointLine.set_coordIndex
]
[from labelInterpolation to MovingVehicleLabelText.string
]
-->
<Script DEF='WaypointTrackScript' directOutput='true'> |
| 71 | <field name='description' type='SFString' accessType='initializeOnly'/> |
| 72 | <field name='waypoints' type='MFVec3f' accessType='initializeOnly'/> |
| 73 | <field name='add_waypoint' type='SFVec3f' accessType='inputOnly'/> |
| 74 | <field name='set_waypoints' type='MFVec3f' accessType='inputOnly'/> |
| 75 | <field name='pitchUpDownForVerticalWaypoints' type='SFBool' accessType='initializeOnly'/> |
| 76 | <field name='legSpeeds' type='MFFloat' accessType='initializeOnly'/> |
| 77 | <field name='legDurations' type='MFTime' accessType='initializeOnly'/> |
| 78 | <field name='defaultSpeed' type='SFFloat' accessType='initializeOnly'/> |
| 79 | <field name='turningRate' type='SFFloat' accessType='initializeOnly'/> |
| 80 | <field name='totalDuration' type='SFTime' accessType='outputOnly'/> |
| 81 | <field name='WaypointPI' type='SFNode' accessType='initializeOnly'> |
| 82 | <PositionInterpolator USE='WaypointPI.instance'/> |
| 83 | </field> |
| 84 | <field name='WaypointOI' type='SFNode' accessType='initializeOnly'> |
| 85 | <OrientationInterpolator USE='WaypointOI.instance'/> |
| 86 | </field> |
| 87 | <field name='pointIndices' type='MFInt32' accessType='outputOnly'/> |
| 88 | <field name='OutputLabelsGroup' type='SFNode' accessType='initializeOnly'> |
| 89 | <Group USE='CoordinateLabelsAndViewpointsGroup'/> |
| 90 | </field> |
| 91 | <field name='set_fraction' type='SFFloat' accessType='inputOnly'/> |
| 92 |
<field name='highlightCoordinates' type='MFVec3f' accessType='outputOnly'
appinfo='Initialized to (0 0 0 0 0 0)'/> |
| 93 | <field name='heightLabel' type='SFString' accessType='initializeOnly'/> |
| 94 | <field name='labelDisplayMode' type='SFString' accessType='initializeOnly'/> |
| 95 | <field name='labelOffset' type='SFVec3f' accessType='initializeOnly'/> |
| 96 | <field name='labelFontSize' type='SFFloat' accessType='initializeOnly'/> |
| 97 | <field name='labelColor' type='SFColor' accessType='initializeOnly'/> |
| 98 | <field name='labelInterpolation' type='MFString' accessType='outputOnly'/> |
| 99 | <field name='traceEnabled' type='SFBool' accessType='initializeOnly'/> |
| 100 |
<field name='outputInitializationComputations' type='SFBool' accessType='initializeOnly'
appinfo='Output the number of waypoints totalDistance and totalDuration to console upon initialization'/> |
| 101 | <!-- local variables (do not use internal var declarations) for persistence --> |
| 102 |
<field name='scriptError' type='SFBool' value='false' accessType='initializeOnly'
appinfo='whether or not an error was detected during script processing.'/> |
| 103 |
<field name='previousFractionIndex' type='SFInt32' value='0' accessType='initializeOnly'
appinfo='retain state information while constructing fraction array'/> |
| 104 |
<field name='depthString' type='SFString' accessType='initializeOnly'
appinfo='label'/> |
| 105 |
<field name='whichRotationVersion' type='SFString' accessType='initializeOnly'
appinfo='label'/> |
| 106 | <field name='verticalDropLineIndices' type='MFInt32' accessType='outputOnly'/> |
| 107 | <field name='verticalDropLinePoints' type='MFVec3f' accessType='outputOnly'/> |
| 108 | <field name='positionKey' type='MFFloat' value='0' accessType='initializeOnly'/> |
| 109 | <field name='positionKeyValueArray' type='MFVec3f' accessType='initializeOnly'/> |
| 110 | <field name='finalPositionKey' type='MFFloat' accessType='outputOnly'/> |
| 111 | <field name='finalPositionKeyValueArray' type='MFVec3f' accessType='outputOnly'/> |
| 112 | <field name='distances' type='MFFloat' accessType='initializeOnly'/> |
| 113 | <field name='pointIndicesAccumulator' type='MFInt32' accessType='initializeOnly'/> |
| 114 | <field name='verticalDropLineIndicesAccumulator' type='MFInt32' accessType='initializeOnly'/> |
| 115 | <field name='verticalDropLinePointsAccumulator' type='MFVec3f' accessType='initializeOnly'/> |
| 116 | <field name='totalDistance' type='SFFloat' value='0' accessType='initializeOnly'/> |
| 117 | <field name='orientations' type='MFRotation' accessType='initializeOnly'/> |
| 118 | <field name='dx' type='SFFloat' value='0' accessType='initializeOnly'/> |
| 119 | <field name='dy' type='SFFloat' value='0' accessType='initializeOnly'/> |
| 120 | <field name='dz' type='SFFloat' value='0' accessType='initializeOnly'/> |
| 121 | <field name='legDistance' type='SFFloat' value='0' accessType='initializeOnly'/> |
| 122 | <field name='heading' type='SFFloat' value='0' accessType='initializeOnly'/> |
| 123 | <field name='pitchAngle' type='SFFloat' value='0' accessType='initializeOnly'/> |
| 124 | <field name='orientationKey' type='MFFloat' accessType='initializeOnly'/> |
| 125 | <field name='newKey' type='MFFloat' accessType='initializeOnly'/> |
| 126 | <field name='newKeyValue' type='MFRotation' accessType='initializeOnly'/> |
| 127 | <field name='outputChild' type='MFNode' accessType='initializeOnly'> |
| 128 | <!-- NULL --> |
| 129 | </field> |
| 130 | <field name='rotatedVector' type='SFVec3f' value='0 0 0' accessType='initializeOnly'/> |
| 131 | <IS> |
| 132 | <connect nodeField='description' protoField='description'/> |
| 133 | <connect nodeField='waypoints' protoField='waypoints'/> |
| 134 | <connect nodeField='add_waypoint' protoField='add_waypoint'/> |
| 135 | <connect nodeField='set_waypoints' protoField='set_waypoints'/> |
| 136 | <connect nodeField='pitchUpDownForVerticalWaypoints' protoField='pitchUpDownForVerticalWaypoints'/> |
| 137 | <connect nodeField='legSpeeds' protoField='legSpeeds'/> |
| 138 | <connect nodeField='legDurations' protoField='legDurations'/> |
| 139 | <connect nodeField='defaultSpeed' protoField='defaultSpeed'/> |
| 140 | <connect nodeField='turningRate' protoField='turningRate'/> |
| 141 | <connect nodeField='totalDuration' protoField='totalDuration'/> |
| 142 | <connect nodeField='set_fraction' protoField='set_fraction'/> |
| 143 | <connect nodeField='heightLabel' protoField='heightLabel'/> |
| 144 | <connect nodeField='labelDisplayMode' protoField='labelDisplayMode'/> |
| 145 | <connect nodeField='labelOffset' protoField='labelOffset'/> |
| 146 | <connect nodeField='labelFontSize' protoField='labelFontSize'/> |
| 147 | <connect nodeField='labelColor' protoField='labelColor'/> |
| 148 | <connect nodeField='traceEnabled' protoField='traceEnabled'/> |
| 149 | <connect nodeField='outputInitializationComputations' protoField='outputInitializationComputations'/> |
| 150 | </IS> |
<![CDATA[
ecmascript:
function tracePrint (outputValue)
{
if (traceEnabled) forcePrint (outputValue);
}
function forcePrint (outputValue)
{
// try to ensure outputValue is converted to string despite browser idiosyncracies
outputString = outputValue.toString(); // utility function according to spec
if (outputString == null) outputString = outputValue; // direct cast
Browser.println ('[WaypointInterpolator ' + description + '] ' + outputString);
}
function distance (p1, p2)
{
return Math.sqrt (
(p2.x - p1.x) * (p2.x - p1.x) +
(p2.y - p1.y) * (p2.y - p1.y) +
(p2.z - p1.z) * (p2.z - p1.z));
}
function normalize2Pi (angle)
{
twoPi = 2 * Math.PI;
x = angle;
while (x >= twoPi) x = x - twoPi;
while (x < 0) x = x + twoPi;
return x;
}
function normalizePi (angle)
{
twoPi = 2 * Math.PI;
x = angle;
while (x >= Math.PI) x = x - twoPi;
while (x < -Math.PI) x = x + twoPi;
return x;
}
function degrees (angle)
{
return angle * 180.0 / Math.PI;
}
function radians (theta)
{
return theta * Math.PI / 180.0;
}
function initialize ()
{
saveTrace = traceEnabled;
traceEnabled = true; // debug use
outputInitializationComputations = true; // debug use
scriptError = false;
traceEnabled= false; // set traceEnabled=true for selective debug during initialization only
forcePrint ('initializing new ' + waypoints.length + '-point WaypointInterpolator ' + description);
tracePrint ('Browser.name =' + Browser.name);
tracePrint ('WaypointPI.key =' + WaypointPI.key.toString());
tracePrint ('WaypointPI.keyValue=' + WaypointPI.keyValue.toString());
// TODO forcePrint ('Returning, initialization trace complete.');
// TODO return;
previousFractionIndex = -1;
tracePrint ('waypoints =' + waypoints.toString());
if ((waypoints.length == 2) &&
(waypoints[0].x == 0) && (waypoints[0].y == 0) && (waypoints[0].z == 0) &&
(waypoints[1].x == 0) && (waypoints[1].y == 0) && (waypoints[1].z == 0))
{
tracePrint ('[default waypoints, no action needed]');
return;
}
if (waypoints.length < 2)
{
forcePrint ('*** error: insufficient waypoints, WaypointInterpolator ignored ***');
scriptError=true;
return;
}
if ( heightLabel.toLowerCase()!='altitude' &&
heightLabel.toLowerCase()!='depth' &&
heightLabel.toLowerCase()!='none')
{
forcePrint ('*** error, heightLabel =' + heightLabel + ', allowed values (none, altitude, depth) ***');
heightLabel ='none';
}
useDefaultSpeed = false; // initialize booleans
useLegSpeeds = false;
useLegDurations = false;
if ((legSpeeds.length == 0) && (legDurations.length == 0)) // use defaultSpeed
{
tracePrint ('defaultSpeed =' + defaultSpeed.toString() + ' meters/second');
if (defaultSpeed <= 0)
{
forcePrint ('*** error, defaultSpeed <= 0 ***');
scriptError=true;
return;
}
else
{
useDefaultSpeed = true;
tracePrint ('useDefaultSpeed = true');
}
}
else if (legSpeeds.length > 0)
{
tracePrint ('legSpeeds =' + legSpeeds.toString() + ' meters/second');
if (legSpeeds.length != waypoints.length - 1)
{
forcePrint ('*** error, legSpeeds.length (' + legSpeeds.length + ' must be one less than waypoints.length (' + waypoints.length + ') ***');
scriptError=true;
return;
}
for (i = 0; i < legSpeeds.length; i++)
{
if (legSpeeds[i] <= 0)
{
forcePrint ('*** error, legSpeeds[' + i + '] zero or negative ***');
scriptError=true;
return;
}
}
if (legDurations.length > 0)
tracePrint ('warning: legDurations ignored, useLegSpeeds=true');
else tracePrint ('useLegSpeeds=true');
useLegSpeeds=true;
}
else // legDurations.length > 0
{
// Xj3D X3DFieldreader.java line 1920: parse error fails to read MFTime values; PositionInterpolator.key destination uses MFFloat anyway
forcePrint ('legDurations =' + legDurations.toString() + ' seconds');
if ((legDurations.length != 1) && (legDurations.length != waypoints.length - 1))
{
forcePrint ('*** error, legDurations.length must be one less than waypoints.length ***');
scriptError=true;
return;
}
for (i = 0; i < legDurations.length; i++)
{
if (legDurations[i] < 0)
{
legDurations[i] = Math.abs(legDurations[i]);
forcePrint ('*** error, legDurations[' + i + ']= -' + legDurations[i]
+ ' is less than zero ***');
scriptError=true;
return;
}
else if (legDurations[i] == 0)
{
forcePrint ('*** Warning, zero value encountered/ignored: ' +
'legDurations[' + i + '] =' + legDurations[i]);
}
}
tracePrint ('useLegDurations=true');
useLegDurations=true;
}
positionKeyValueArray = waypoints;
for (i = 0; i < (waypoints.length - 1); i++)
{
distances[i] = Math.sqrt (
(waypoints[i+1].x - waypoints[i].x) * (waypoints[i+1].x - waypoints[i].x) +
(waypoints[i+1].y - waypoints[i].y) * (waypoints[i+1].y - waypoints[i].y) +
(waypoints[i+1].z - waypoints[i].z) * (waypoints[i+1].z - waypoints[i].z));
totalDistance += distances[i];
pointIndicesAccumulator[i]= i;
}
forcePrint ('distances =' + distances.toString() + ' meters');
forcePrint ('totalDistance =' + Math.round (totalDistance * 10)/10 + ' meters');
pointIndicesAccumulator[waypoints.length - 1]= waypoints.length - 1;
pointIndicesAccumulator[waypoints.length] = -1;
for (i = 0; i < (waypoints.length ); i++)
{
verticalDropLineIndicesAccumulator[3*i] = 2*i;
verticalDropLineIndicesAccumulator[3*i+ 1] = 2*i + 1;
verticalDropLineIndicesAccumulator[3*i+ 2] = -1;
verticalDropLinePointsAccumulator[2*i] = waypoints[i];
verticalDropLinePointsAccumulator[2*i+1] = new SFVec3f(waypoints[i].x, 0.0, waypoints[i].z);
}
pointIndices = pointIndicesAccumulator;
tracePrint ('pointIndices =' + pointIndices.toString());
verticalDropLineIndices = verticalDropLineIndicesAccumulator;
tracePrint ('verticalDropLineIndices =' + verticalDropLineIndices.toString());
verticalDropLinePoints = verticalDropLinePointsAccumulator;
tracePrint ('verticalDropLinePoints =' + verticalDropLinePoints.toString());
totalDurationAccumulator = 0.0;
for (i = 0; i < (waypoints.length - 1); i++)
{
if (useDefaultSpeed)
{
totalDurationAccumulator += distances[i] / defaultSpeed;
}
else if (useLegSpeeds)
{
totalDurationAccumulator += distances[i] / legSpeeds[i];
}
else // useLegDurations
{
totalDurationAccumulator += legDurations[i];
// forcePrint ('legDurations[' + i + ']=' + legDurations[i]);
// forcePrint ('totalDurationAccumulator=' + totalDurationAccumulator + ' seconds');
}
}
totalDuration = totalDurationAccumulator; // send SFTime eventOut
hours = Math.floor (totalDuration / 3600.0); // % is modulo operator, provides remainder
minutes = Math.floor ((totalDuration - hours * 3600) / 60.0);
seconds = Math.round ((totalDuration - hours * 3600 - minutes * 60) * 10) / 10; // 0.1 sec resolution
if (totalDuration <= 0)
{
forcePrint ('*** error: totalDuration=' + totalDuration + ' seconds (' +
hours + ' hours,' + minutes + ' minutes,' + seconds + ' seconds)');
scriptError=true;
return;
}
else if (outputInitializationComputations)
forcePrint ('totalDuration =' + Math.round (totalDuration * 10)/10 + ' seconds (' +
hours + ' hours,' + minutes + ' minutes,' + seconds + ' seconds)');
positionKey[0] = 0;
for (i = 1; i < waypoints.length; i++)
{
if (useDefaultSpeed)
{
positionKey[i] = i / (waypoints.length - 1); // simple fraction
}
else if (useLegSpeeds)
{
positionKey[i] = ((distances[i-1] / legSpeeds[i-1]) / totalDuration) + positionKey[i-1];
}
else // useLegDurations
{
positionKey[i] = (legDurations[i-1] / totalDuration) + positionKey[i-1];
}
}
positionKey[waypoints.length-1] = 1.0; // avoid roundup greater than 1.0
tracePrint ('positionKey.length =' + positionKey.length);
tracePrint ('positionKey =' + positionKey.toString());
tracePrint ('positionKeyValueArray.length =' + positionKeyValueArray.length);
tracePrint ('positionKeyValueArray =' + positionKeyValueArray.toString());
// directly set event
WaypointPI.key = positionKey;
WaypointPI.keyValue = positionKeyValueArray;
tracePrint ('WaypointPI.key =' + WaypointPI.key.toString());
tracePrint ('WaypointPI.keyValue =' + WaypointPI.keyValue.toString());
// ROUTE outputOnly event
finalPositionKey = positionKey;
finalPositionKeyValueArray = positionKeyValueArray;
tracePrint ('finalPositionKey =' + finalPositionKey.toString());
tracePrint ('finalPositionKeyValueArray =' + finalPositionKeyValueArray.toString());
tracePrint ('WaypointPI.key =' + WaypointPI.key.toString());
tracePrint ('WaypointPI.keyValue =' + WaypointPI.keyValue.toString());
tracePrint ('pitchUpDownForVerticalWaypoints=' + pitchUpDownForVerticalWaypoints);
// different approaches to orientation calculations
whichRotationVersion ='FirstHeadingThenPitchStayVertical';
//'IndependentLegOrientations';
//'RelativeLegOrientations';
//'FirstHeadingThenPitchStayVertical';
tracePrint ('whichRotationVersion=' + whichRotationVersion);
// SFRotation constructor for two Vector3Arrays returns rotation from first to second
// default body axis is along X axis
// TODO avoid changing value if normalized vector has length 0 (meaning no direction change)
orientations = new MFRotation();
orientations[0] = new SFRotation (new SFVec3f (1, 0, 0),
waypoints[1].subtract(waypoints[0]).normalize()); // first leg
dx = waypoints[1].x - waypoints[0].x;
dy = waypoints[1].y - waypoints[0].y;
dz = waypoints[1].z - waypoints[0].z;
legDistance = Math.sqrt (dx*dx + dy*dy + dz*dz);
levelDistance = Math.sqrt (dx*dx + dz*dz);
tracePrint ('dx=' + dx + ', dy=' + dy + ', dz=' + dz + ', legDistance=' + legDistance + ', levelDistance=' + levelDistance);
tracePrint ('orientations[0] =' + orientations[0].toString());
for (i = 1; i < (waypoints.length - 1); i++) // compute orientations array
{
dx = waypoints[i+1].x - waypoints[i].x;
dy = waypoints[i+1].y - waypoints[i].y;
dz = waypoints[i+1].z - waypoints[i].z;
legDistance = Math.sqrt (dx*dx + dy*dy + dz*dz);
levelDistance = Math.sqrt (dx*dx + dz*dz);
tracePrint ('dx=' + dx + ', dy=' + dy + ', dz=' + dz +
', legDistance=' + Math.round ( legDistance*10)/10 +
', levelDistance=' + Math.round (levelDistance*10)/10);
// tracePrint ('waypoints[i ].subtract(waypoints[i-1]) =' + waypoints[i ].subtract(waypoints[i-1]).toString());
// tracePrint ('waypoints[i+1].subtract(waypoints[i]) =' + waypoints[i+1].subtract(waypoints[i]).toString());
// tracePrint ('dot product=' + waypoints[i+1].subtract(waypoints[i]).normalize().
// dot(waypoints[i].subtract(waypoints[i-1]).normalize()).toString());
if (whichRotationVersion=='IndependentLegOrientations')
{
tracePrint ('whichRotationVersion==IndependentLegOrientations');
// using constructor SFRotation (SFVec3f fromVector, SFVec3f toVector)
// see X3D ECMAScript binding Table 7.18 — SFRotation instance creation functions
// buggy: can twist/roll unpredictably about relative-x axis
// apparently a CosmoPlayer bug in SFRotation constructor when pointing (-1, 0, 0)
// TODO test if difference vector is zero, if so maintain previous rotation
orientations[i] = new SFRotation (
new SFVec3f (1, 0, 0),
waypoints[i+1].subtract(waypoints[i]).normalize());
}
else if (whichRotationVersion=='RelativeLegOrientations')
{
tracePrint ('whichRotationVersion==IndependentLegOrientations');
orientations[i] = new SFRotation (
waypoints[i ].subtract(waypoints[i-1]).normalize(),
waypoints[i+1].subtract(waypoints[i]).normalize());
// orientation multiplication (i.e. composition) is order dependent
orientations[i] = orientations[i-1].multiply (orientations[i]); // relative to previous leg
}
else if (whichRotationVersion=='FirstHeadingThenPitchStayVertical')
{
if ( (Math.abs(legDistance) <= 0.00001) ||
((Math.abs(levelDistance) <= 0.00001) && (pitchUpDownForVerticalWaypoints == false)))
{
tracePrint ('whichRotationVersion==FirstHeadingThenPitchStayVertical, coincident');
if (legDistance <= 0.00001)
tracePrint ('...staying in one place');
else
tracePrint ('...maintaining orientation during vertical motion');
orientations[i] = orientations[i-1];
}
else if (levelDistance <= 0.00001) // pitch up/down along vertical axis
{
tracePrint ('whichRotationVersion==FirstHeadingThenPitchStayVertical, pitch up/down along vertical axis');
// still twisting about roll axis, unfortunately...
if (waypoints[i+1].y > waypoints[i].y) // or test dy
{
tracePrint ('...pitching up vertical axis');
orientations[i] = new SFRotation (
waypoints[i].subtract(waypoints[i-1]).normalize(),
new SFVec3f (0, 1, 0)); // relative
}
else
{
tracePrint ('...pitching down vertical axis');
orientations[i] = new SFRotation (
waypoints[i].subtract(waypoints[i-1]).normalize(),
new SFVec3f (0, -1, 0)); // relative
}
orientations[i] = orientations[i-1].multiply (orientations[i]); // relative to previous leg
}
else // carefully rotate about Y axis then pitch up/down to avoid unpredictable twists/rolls
{
tracePrint ('whichRotationVersion==FirstHeadingThenPitchStayVertical, carefully rotate about Y axis etc.');
heading = Math.atan2 (dz, dx); // atan2 returns arctangent in any of 4 quadrants
orientations[i] = new SFRotation (0, 1, 0, -heading); // note negation
// can go vertical if preferred, levelDistance == 0 cases handled above
pitchAngle = Math.atan (dy / levelDistance); // negative angle should pitch down, note no negation
// orientation multiplication (i.e. composition) is order dependent
// !! this is the step that causes a Cosmo/Cortona sign error !!
// it is due to opposite responses to multiplication order.
tempHold = orientations[i]; // not assuming that browser self-multiplication is safe
if (Browser.name=='CosmoPlayer') // reverse multiplication order for old browser
orientations[i] = (new SFRotation (0, 0, 1, pitchAngle)).multiply (tempHold); // mod heading
else orientations[i] = tempHold.multiply (new SFRotation (0, 0, 1, pitchAngle)); // mod heading
tracePrint ('heading=' + Math.round (degrees (heading) *10)/10 + ' degrees,' +
' pitchAngle=' + Math.round (degrees (pitchAngle)*10)/10 + ' degrees');
}
}
else if (Math.abs(legDistance) <= 0.00001)
{
tracePrint ('coincident waypoints, set orientations[' + i + '] = orientations[' + i-1 + ']');
orientations[i] = orientations[i-1];
}
else
{
forcePrint ('*** unexpected case trapped, set orientations[' + i + '] = orientations[' + i-1 + ']');
orientations[i] = orientations[i-1];
}
tracePrint ('orientations[' + i + '] =' + orientations[i].toString());
}
// traceEnabled = true; // debug
// full array trace
tracePrint ('orientations =' + orientations.toString());
if (orientations.length != (waypoints.length - 1))
{
forcePrint ('** computation error: orientations.length=' + orientations.length + ' mismatch with waypoints.length=' + waypoints.length);
}
if (turningRate < 0)
{
forcePrint ('** error: negative value for turningRate illegal, making turningRate positive');
turningRate = -turningRate;
}
tracePrint ('turningRate =' + turningRate + ' degrees/second');
orientationKey = new MFFloat ();
orientationKey[0] = 0;
for (i = 1; i < (waypoints.length-1); i++)
{
deltaAngle = orientations[i].multiply(orientations[i-1].inverse()).angle;
deltaAngle = normalizePi (deltaAngle);
turnTime = Math.abs (deltaAngle) / radians (turningRate);
tracePrint ('deltaAngle[' + i + ']=' + degrees (deltaAngle) + ' degrees, turnTime=' + turnTime);
precedingLegDuration = (positionKey[i] - positionKey[i-1]) * totalDuration;
followingLegDuration = (positionKey[i+1] - positionKey[i] ) * totalDuration;
// turn for no more than 1/3 of preceding or following leg durations, respectively
precedingTurnKeyOffset = Math.min (turnTime/2, precedingLegDuration/3) / totalDuration;
followingTurnKeyOffset = Math.min (turnTime/2, followingLegDuration/3) / totalDuration;
tracePrint ('precedingTurnKeyOffset=' + (precedingTurnKeyOffset * totalDuration) + ' seconds');
tracePrint ('followingTurnKeyOffset=' + (followingTurnKeyOffset * totalDuration) + ' seconds');
orientationKey[3*i - 2] = positionKey[i] - precedingTurnKeyOffset;
orientationKey[3*i - 1] = positionKey[i];
orientationKey[3*i] = positionKey[i] + followingTurnKeyOffset;
if (orientationKey[3*i - 2] <= positionKey[i-1]) // interpolate preceding key if needed
{
orientationKey[3*i - 2] = positionKey[i-1] + ((positionKey[i] - positionKey[i-1]) * 2 / 3);
}
if (orientationKey[3*i] >= positionKey[i+1]) // interpolate following key if needed
{
orientationKey[3*i] = positionKey[i] + ((positionKey[i+1] - positionKey[i]) * 1 / 3);
}
if ((orientationKey[3*i - 2] > orientationKey[3*i - 1]) || (orientationKey[3*i - 1] > orientationKey[3*i]))
{
forcePrint ('** error computing orientationKey [' + (3*i - 2) + '..' + (3*i) + ']');
}
}
orientationKey[3*(waypoints.length-1)-2] = 1.0; // avoid roundup greater than 1
tracePrint ('orientationKey.length =' + orientationKey.length);
tracePrint ('orientationKey =' + orientationKey.toString());
//
for (i = 2; i < (orientationKey.length-1); i++)
{
if (orientationKey [i-1] > orientationKey [i])
forcePrint ('*** error,' +
'orientationKey [' + (i-1) + ']=' + orientationKey [i-1].toString() + ',' +
'orientationKey [' + (i) + ']=' + orientationKey [i].toString() +
' values are not monotonically increasing ***');
if ((orientationKey [i] < 0) || (orientationKey [i] > 1))
forcePrint ('*** error, orientationKey [' + i + ']=' + orientationKey [i].toString() +
' value is out of range [0..1] ***');
}
tracePrint ('check orientationKey complete, dynamically building orientationKeyValueArray next');
orientationKeyValueArray = new MFRotation ();
orientationKeyValueArray[0] = orientations[0];
orientationKeyValueArray[1] = orientations[0];
for (i = 1; i < (waypoints.length - 1); i++)
{
// spherical linear interpolation (slerp) 0.5 interpolates halfway between adjacent orientations
orientationKeyValueArray[3*i - 1] = orientations[i-1].slerp(orientations[i], 0.5);
orientationKeyValueArray[3*i] = orientations[i];
orientationKeyValueArray[3*i + 1] = orientations[i]; // straight-line track, same orientation
}
tracePrint ('orientationKeyValueArray.length =' + orientationKeyValueArray.length);
tracePrint ('orientationKeyValueArray =' + orientationKeyValueArray.toString());
// eliminate orientationKey triplicates (smaller arrays overcome CosmoPlayer overflow bug)
newKey = new MFFloat ();
newKey [0] = orientationKey [0];
newKey [1] = orientationKey [1];
newKeyValue = new MFRotation ();
newKeyValue [0] = orientationKeyValueArray [0];
newKeyValue [1] = orientationKeyValueArray [1];
index = 2; // keep first two orientations identical, index is for next value
for (i = 2; i < (orientationKeyValueArray.length-3) ; i++)
{
dotProductBA = orientationKeyValueArray [i-1].getAxis().dot(orientationKeyValueArray [i-2].getAxis());
dotProductCB = orientationKeyValueArray [i].getAxis().dot(orientationKeyValueArray [i-1].getAxis());
angleDifferenceBA = normalizePi(
normalize2Pi (orientationKeyValueArray [i-1].angle) -
normalize2Pi (orientationKeyValueArray [i-2].angle)) * 180 / Math.PI;
angleDifferenceCB = normalizePi(
normalize2Pi (orientationKeyValueArray [i].angle) -
normalize2Pi (orientationKeyValueArray [i-1].angle)) * 180 / Math.PI;
if (i < 10) // too many outputs clobbers the trace console
{
tracePrint ('orientationKeyValueArray [' + (i-2) + ']=' + orientationKeyValueArray [i-2].toString());
tracePrint ('orientationKeyValueArray [' + (i-1) + ']=' + orientationKeyValueArray [i-1].toString());
tracePrint ('orientationKeyValueArray [' + (i ) + ']=' + orientationKeyValueArray [i ].toString());
tracePrint ('dotProductBA =' + dotProductBA + ', dotProductCB =' + dotProductCB);
tracePrint ('angleDifferenceBA=' + angleDifferenceBA + ', angleDifferenceBC=' + angleDifferenceCB + ' degrees');
}
// // depth check also needed! but positionKey is already optimized/compressed, so how to check?
// if ((Math.abs (dotProductCB - 1) < 0.01) &&
// (Math.abs (dotProductBA - 1) < 0.01) &&
// (Math.abs (angleDifferenceCB) < 1.0 ) &&
// (Math.abs (angleDifferenceBA) < 1.0 )) // degrees
// {
// // replace key time with later value
// tracePrint ('... matching this orientationKey time,' +
// 'updating key' + newKey [index-1] + ' to' + orientationKey [i]);
// newKey [index-1] = orientationKey [i];
// // don't update orientation in order to avoid creeping matches
// }
// else
// {
newKey [index] = orientationKey [i];
newKeyValue [index] = orientationKeyValueArray [i];
index ++;
tracePrint ('... keeping this orientationKeyValue');
// }
if (newKey [index-2] > newKey [index-1])
forcePrint ('*** error,' +
'newKey [' + (index-2) + ']=' + newKey [index-2].toString() + ',' +
'newKey [' + (index-1) + ']=' + newKey [index-1].toString() +
' values are not monotonically increasing ***');
if ((newKey [index-1] < 0) || (newKey [index-1] > 1))
forcePrint ('*** error, newKey [' + (index-1) + ']=' + newKey [index-1].toString() +
' value is out of range [0..1] ***');
}
newKey [index] = orientationKey [orientationKeyValueArray.length-2]; // match finals values
newKeyValue [index] = orientationKeyValueArray [orientationKeyValueArray.length-2];
index++;
newKey [index] = orientationKey [orientationKeyValueArray.length-1]; // match finals values
newKeyValue [index] = orientationKeyValueArray [orientationKeyValueArray.length-1];
tracePrint ('orientation newKey.length =' + newKey.length);
tracePrint ('orientation newKey =' + newKey.toString());
tracePrint ('orientation newKeyValue.length =' + newKeyValue.length);
tracePrint ('orientation newKeyValue =' + newKeyValue.toString());
WaypointOI.key = newKey;
WaypointOI.keyValue = newKeyValue;
tracePrint ('WaypointOI.key =' + WaypointOI.key.toString());
tracePrint ('WaypointOI.keyValue =' + WaypointOI.keyValue.toString());
tracePrint ('labelDisplayMode=' + labelDisplayMode);
if (labelDisplayMode.toLowerCase() =='waypoints')
{
// create text labels for each waypoint
outputChild = new MFNode ();
outputVrmlString ='';
for (i = 0; i < waypoints.length; i++)
{
textOffset = waypoints[i].add(labelOffset);
if ((i == waypoints.length-1) && (waypoints[i].x == waypoints[0].x) &&
(waypoints[i].y == waypoints[0].y) && (waypoints[i].z == waypoints[0].z))
// double offset for endpoint when waypoints are a loop
textOffset = textOffset.subtract(new SFVec3f (0, 3 * labelFontSize, 0));
hours = Math.floor (totalDuration * positionKey[i] / 3600.0); // % is modulo operator, provides remainder
minutes = Math.floor ((totalDuration * positionKey[i] - hours * 3600.0) / 60.0);
seconds = Math.round (totalDuration * positionKey[i] - hours * 3600.0 - minutes * 60.0);
while (minutes >= 60)
{
minutes -= 60;
hours += 1;
}
while (seconds >= 60)
{
seconds -= 60;
minutes += 1;
}
if (hours < 10) hours ='0' + hours;
if (minutes < 10) minutes ='0' + minutes;
if (seconds < 10) seconds ='0' + seconds;
locationX = Math.round (waypoints[i].x);
depth = -Math.round (waypoints[i].y * 10) / 10;
locationZ = Math.round (waypoints[i].z);
if (heightLabel.toLowerCase()=='altitude')
depthString = (-depth) + ' ';
else if (heightLabel.toLowerCase()=='depth')
depthString = depth + ' ';
else if (heightLabel.toLowerCase()=='none')
depthString =' ';
else depthString =' ';
outputVrmlString +=
'Transform { translation' + textOffset + '\n'
+ ' children LOD { range [' + 150 * labelFontSize + ' ]\n'
+ ' level [\n'
+ ' Billboard { axisOfRotation 0 1 0 \n'
+ ' children Shape {\n'
+ ' geometry Text {\n'
+ ' string [ \"' + hours + ':' + minutes + ':' + seconds + '\"\n'
+ ' \"' + locationX + ' ' + depthString + locationZ + ' ' + '\" ]\n'
+ ' fontStyle DEF WPIFontStyle FontStyle {\n'
+ ' size' + labelFontSize + '\n'
+ ' justify [ \"MIDDLE\" \"MIDDLE\" ]\n'
+ ' }\n'
+ ' }\n'
+ ' appearance DEF WPIAppearance Appearance {\n'
+ ' material Material { diffuseColor' + labelColor + ' }\n'
+ ' }\n'
+ ' }\n'
+ ' }\n'
+ ' WorldInfo { } ]\n'
+ ' }\n'
+ '}\n';
}
tracePrint ('outputVrmlString=' + outputVrmlString);
outputChild = Browser.createVrmlFromString (outputVrmlString);
OutputLabelsGroup.addChildren = outputChild;
// tracePrint ('OutputLabelsGroup.children =');
// tracePrint (outputChild + ' ' + OutputLabelsGroup.children.toString());
}
else if (labelDisplayMode.toLowerCase() =='interpolation')
{
// updates occur when fraction changes
}
else if ((labelDisplayMode.toLowerCase() !='none') && (labelDisplayMode !=''))
{
forcePrint ('*** illegal value labelDisplayMode=' + labelDisplayMode + ', ignored');
}
if (outputInitializationComputations)
{
tracePrint ('initialization complete');
forcePrint ('=======================================');
}
traceEnabled = saveTrace;
} // end of initialize() method
function set_fraction (fractionValue, timeStamp)
{
tracePrint ('fractionValue=' + fractionValue);
tracePrint ('previousFractionIndex=' + previousFractionIndex);
tracePrint ('WaypointPI.value_changed=' + WaypointPI.value_changed.toString());
tracePrint ('WaypointOI.value_changed=' + WaypointOI.value_changed.toString());
if (scriptError==true)
{
tracePrint ('scriptError==true, no response by set_fraction()');
return;
}
// tracePrint ('WaypointPI.key =' + WaypointPI.key.toString());
// tracePrint ('WaypointPI.keyValue =' + WaypointPI.keyValue.toString());
// wide input range supported by interpolators,
// usually no range check on fractionValue.
// however WaypointInterpolator input range is [0..1], so check
if ((fractionValue < 0) || (fractionValue > 1))
{
forcePrint ('*** error: set_fraction=' + fractionValue + ' out of range [0..1], ignored');
return;
}
if (previousFractionIndex == -1)
{
previousFractionIndex = 0; // start
while (fractionValue >= positionKey[previousFractionIndex+1])
{
previousFractionIndex ++;
if (previousFractionIndex >= waypoints.length - 2) break;
}
highlightCoordinates = new MFVec3f (waypoints[previousFractionIndex],
waypoints[previousFractionIndex +1]);
tracePrint ('highlightCoordinates=' + highlightCoordinates.toString());
}
else if (waypoints.length == 2)
{
// only one segment, no action required
}
else if (previousFractionIndex == waypoints.length - 2) // last leg
{
if (fractionValue < positionKey[previousFractionIndex]) // looped
{
previousFractionIndex = 0; // start
while (fractionValue >= positionKey[previousFractionIndex+1])
{
previousFractionIndex ++;
if (previousFractionIndex >= waypoints.length - 2) break;
}
highlightCoordinates = new MFVec3f (waypoints[previousFractionIndex],
waypoints[previousFractionIndex +1]);
tracePrint ('highlightCoordinates=' + highlightCoordinates.toString());
}
}
else if (fractionValue >= positionKey[previousFractionIndex+1])
{
previousFractionIndex++;
while (fractionValue >= positionKey[previousFractionIndex+1])
{
previousFractionIndex ++;
if (previousFractionIndex >= waypoints.length - 2) break;
}
if (previousFractionIndex > waypoints.length - 2) previousFractionIndex = 0;
highlightCoordinates = new MFVec3f (
waypoints[previousFractionIndex],
waypoints[previousFractionIndex+1]);
tracePrint ('highlightCoordinates=' + highlightCoordinates.toString());
}
// else previousFractionIndex ought to be OK
if (labelDisplayMode =='interpolation')
{
hours = Math.floor (totalDuration * fractionValue / 3600.0); // % is modulo operator, provides remainder
minutes = Math.floor ((totalDuration * fractionValue - hours * 3600) / 60.0);
seconds = Math.round (totalDuration * fractionValue - hours * 3600 - minutes * 60);
while (minutes > 60)
{
minutes -= 60;
hours += 1;
}
while (seconds > 60)
{
seconds -= 60;
minutes += 1;
}
if (hours < 10) hours ='0' + hours;
if (minutes < 10) minutes ='0' + minutes;
if (seconds < 10) seconds ='0' + seconds;
// compute course and pitch
currentAxis = WaypointOI.value_changed.getAxis().normalize();
currentRotation = WaypointOI.value_changed;
// forcePrint ('=====currentRotation=' + currentRotation.toString() + ', currentAxis=' + currentAxis.toString());
rotatedVector = currentRotation.multVec (new SFVec3f (1, 0, 0)); // rotate x-centered body
dx = rotatedVector.x;
dy = rotatedVector.y;
dz = rotatedVector.z;
levelDistance = Math.sqrt (dx*dx + dz*dz);
heading = Math.atan2 (dz, dx); // atan2 returns arctangent in any of 4 quadrants
if (levelDistance > 0)
pitchAngle = Math.atan (dy / levelDistance); // negative angle should pitch down, note no negation
else if (dy > 0)
pitchAngle = 1.57;
else pitchAngle = -1.57;
// forcePrint ('rotatedVector=' + rotatedVector.toString());
// forcePrint ('heading=' + degrees(heading) + ', pitchAngle=' + degrees(pitchAngle));
course = Math.round (normalize2Pi ( heading) * 180 / Math.PI);
pitch = Math.round (normalizePi ( pitchAngle) * 180 / Math.PI);
// format angles in degrees
if (course < 10) course = '0' + '0' + course;
else if (course < 100) course = '0' + course;
// tracePrint ('course=' + course + ', pitch=' + pitch);
locationX = Math.round (WaypointPI.value_changed.x);
depth = -Math.round (WaypointPI.value_changed.y * 10) / 10;
locationZ = Math.round (WaypointPI.value_changed.z);
if (heightLabel.toLowerCase()=='altitude')
depthString =', altitude ' + (-depth) + 'm';
else if (heightLabel.toLowerCase()=='depth')
depthString =', depth ' + depth + 'm';
else if (heightLabel.toLowerCase()=='none')
depthString ='';
else depthString ='';
labelInterpolation = new MFString (
description,
(hours + ':' + minutes + ':' + seconds + ', course=' + course + ', pitch=' + pitch),
('location=(' + locationX + ' ' + locationZ + depthString + ')'));
// tracePrint ('labelInterpolation=' + labelInterpolation);
}
tracePrint ('=====');
return;
}
function add_waypoint (newWaypointsArray, timeStamp)
{
// EcmaScript automatically increases array size
// when setting an element one past final element
waypoints[waypoints.length] = newWaypointsArray;
// initialization code is complicated! so we won't try to shortcut/optimize it, instead just rerun it
initialize ();
}
function set_waypoints (newWaypointsArray, timeStamp)
{
waypoints = newWaypointsArray;
initialize ();
}
]]>
|
|
| 152 | </Script> |
| 153 | < ROUTE fromNode='WaypointTrackScript' fromField='finalPositionKey' toNode='WaypointPI.instance' toField='key'/> |
| 154 | < ROUTE fromNode='WaypointTrackScript' fromField='finalPositionKeyValueArray' toNode='WaypointPI.instance' toField='keyValue'/> |
| 155 | <!-- IndexedLineSet connects waypoints for easy visibility. Set transparency=1 to hide. --> |
| 156 | <Shape DEF='VerticalDropLineShape'> |
| 157 |
<!-- ROUTE information for VerticalDropLine node:
[from WaypointTrackScript.verticalDropLineIndices to set_coordIndex
]
-->
<IndexedLineSet DEF='VerticalDropLine'> |
| 158 |
<!-- ROUTE information for VerticalDropLineCoordinates node:
[from WaypointTrackScript.verticalDropLinePoints to point
]
-->
<Coordinate DEF='VerticalDropLineCoordinates'/> |
| 159 | </IndexedLineSet> |
| 160 | <Appearance> |
| 161 | <Material DEF='VerticalDropLineMaterial'> |
| 162 | <IS> |
| 163 | <connect nodeField='emissiveColor' protoField='verticalDropLineColor'/> |
| 164 | <connect nodeField='transparency' protoField='verticalDropLineTransparency'/> |
| 165 | </IS> |
| 166 | </Material> |
| 167 | </Appearance> |
| 168 | </Shape> |
| 169 | < ROUTE fromNode='WaypointTrackScript' fromField='verticalDropLineIndices' toNode='VerticalDropLine' toField='set_coordIndex'/> |
| 170 | < ROUTE fromNode='WaypointTrackScript' fromField='verticalDropLinePoints' toNode='VerticalDropLineCoordinates' toField='point'/> |
| 171 | <Shape DEF='HighlightShape'> |
| 172 | <IndexedLineSet DEF='HighlightSegment' coordIndex='0 1 -1'> |
| 173 |
<!-- ROUTE information for HighlightSegmentCoordinates node:
[from WaypointTrackScript.highlightCoordinates to point
]
-->
<Coordinate DEF='HighlightSegmentCoordinates' point='0 0 0 0 0 0'/> |
| 174 | </IndexedLineSet> |
| 175 | <Appearance> |
| 176 | <Material DEF='HighlightSegmentMaterial' diffuseColor='0 0 0' emissiveColor='0.2 0.2 0.2'> |
| 177 | <IS> |
| 178 | <connect nodeField='emissiveColor' protoField='highlightSegmentColor'/> |
| 179 | <connect nodeField='transparency' protoField='transparency'/> |
| 180 | </IS> |
| 181 | </Material> |
| 182 | </Appearance> |
| 183 | </Shape> |
| 184 | < ROUTE fromNode='WaypointTrackScript' fromField='highlightCoordinates' toNode='HighlightSegmentCoordinates' toField='point'/> |
| 185 | <Shape DEF='WaypointLineShape'> |
| 186 |
<!-- ROUTE information for WaypointLine node:
[from WaypointTrackScript.pointIndices to set_coordIndex
]
-->
<IndexedLineSet DEF='WaypointLine'> |
| 187 | <Coordinate DEF='WaypointLineCoordinates'> |
| 188 | <IS> |
| 189 | <connect nodeField='point' protoField='waypoints'/> |
| 190 | </IS> |
| 191 | </Coordinate> |
| 192 | </IndexedLineSet> |
| 193 | <Appearance> |
| 194 | <Material DEF='WaypointTrackMaterial' emissiveColor='0.8 0.8 0.8'> |
| 195 | <IS> |
| 196 | <connect nodeField='emissiveColor' protoField='lineColor'/> |
| 197 | <connect nodeField='transparency' protoField='transparency'/> |
| 198 | </IS> |
| 199 | </Material> |
| 200 | </Appearance> |
| 201 | </Shape> |
| 202 | < ROUTE fromNode='WaypointTrackScript' fromField='pointIndices' toNode='WaypointLine' toField='set_coordIndex'/> |
| 203 | <!-- Draw highlight segment before and after waypoint lines in case of order dependency --> |
| 204 | <!-- TODO!! throws Xj3D exception! <Shape USE='HighlightShape'/> --> |
| 205 |
<!-- ROUTE information for MovingVehicleLabel node:
[from WaypointPI.instance.value_changed to translation
]
[from WaypointOI.instance.value_changed to rotation
]
-->
<Transform DEF='MovingVehicleLabel'> |
| 206 | <!-- no need to externally ROUTE position and orientation interpolator key/keyValue results, since prototype is using pass-by-reference node update --> |
| 207 | <!-- Nevertheless, must ROUTE position and orientation interpolated text label --> |
| 208 | < ROUTE fromNode='WaypointPI.instance' fromField='value_changed' toNode='MovingVehicleLabel' toField='translation'/> |
| 209 | < ROUTE fromNode='WaypointOI.instance' fromField='value_changed' toNode='MovingVehicleLabel' toField='rotation'/> |
| 210 | <Transform DEF='MovingVehicleLabelOffset'> |
| 211 | <IS> |
| 212 | <connect nodeField='translation' protoField='labelOffset'/> |
| 213 | </IS> |
| 214 | <Billboard> |
| 215 | <Shape> |
| 216 |
<!-- ROUTE information for MovingVehicleLabelText node:
[from WaypointTrackScript.labelInterpolation to string
]
-->
<Text DEF='MovingVehicleLabelText'> |
| 217 | <FontStyle DEF='MovingVehicleLabelFont' justify='"MIDDLE" "MIDDLE"'> |
| 218 | <IS> |
| 219 | <connect nodeField='size' protoField='labelFontSize'/> |
| 220 | </IS> |
| 221 | </FontStyle> |
| 222 | </Text> |
| 223 | <Appearance> |
| 224 | <Material DEF='MovingVehicleLabelMaterial'> |
| 225 | <IS> |
| 226 | <connect nodeField='diffuseColor' protoField='labelColor'/> |
| 227 | </IS> |
| 228 | </Material> |
| 229 | </Appearance> |
| 230 | </Shape> |
| 231 | < ROUTE fromNode='WaypointTrackScript' fromField='labelInterpolation' toNode='MovingVehicleLabelText' toField='string'/> |
| 232 | </Billboard> |
| 233 | </Transform> |
| 234 | </Transform> |
| 235 | </Group> |
| 236 | </ProtoBody> |
| 237 | </ProtoDeclare> |
| 238 | <!-- ====================================== --> |
| 239 | <Anchor description='WaypointInterpolator Example' url=' "WaypointInterpolatorExample.x3d" "https://www.web3d.org/x3d/content/examples/Savage/Tools/Animation/WaypointInterpolatorExample.x3d" "WaypointInterpolatorExample.wrl" "https://www.web3d.org/x3d/content/examples/Savage/Tools/Animation/WaypointInterpolatorExample.wrl" '> |
| 240 | <Shape> |
| 241 | <Text string='"WaypointInterpolatorPrototype" "defines a prototype" "" "Click on this text to see" "WaypointInterpolatorExample" " scene"'> |
| 242 | <FontStyle justify='"MIDDLE" "MIDDLE"'/> |
| 243 | </Text> |
| 244 | <Appearance> |
| 245 | <Material diffuseColor='1 1 0.2'/> |
| 246 | </Appearance> |
| 247 | </Shape> |
| 248 | <Shape> |
| 249 | <Box size='12 6.0 0.001'/> |
| 250 | <Appearance> |
| 251 | <Material diffuseColor='1 1 1' transparency='1'/> |
| 252 | </Appearance> |
| 253 | </Shape> |
| 254 | </Anchor> |
| 255 | </Scene> |
| 256 | </X3D> |
Event Graph ROUTE Table entries with 9 ROUTE connections total, showing X3D event-model relationships for this scene.
Each row shows an event cascade that may occur during a single timestamp interval between frame renderings, as part of the X3D execution model.
|
WaypointTrackScript
Script finalPositionKey MFFloat |
WaypointPI.instance
PositionInterpolator key MFFloat |
then
|
WaypointPI.instance
PositionInterpolator value_changed SFVec3f |
MovingVehicleLabel
Transform translation SFVec3f |
||
|
WaypointTrackScript
Script finalPositionKeyValueArray MFVec3f |
WaypointPI.instance
PositionInterpolator keyValue MFVec3f |
then
|
WaypointPI.instance
PositionInterpolator value_changed SFVec3f |
MovingVehicleLabel
Transform translation SFVec3f |
||
|
WaypointTrackScript
Script verticalDropLineIndices MFInt32 |
VerticalDropLine
IndexedLineSet set_coordIndex MFInt32 |
|||||
|
WaypointTrackScript
Script verticalDropLinePoints MFVec3f |
VerticalDropLineCoordinates
Coordinate point MFVec3f |
|||||
|
WaypointTrackScript
Script highlightCoordinates MFVec3f |
HighlightSegmentCoordinates
Coordinate point MFVec3f |
|||||
|
WaypointTrackScript
Script pointIndices MFInt32 |
WaypointLine
IndexedLineSet set_coordIndex MFInt32 |
|||||
|
WaypointTrackScript
Script labelInterpolation MFString |
MovingVehicleLabelText
Text string MFString |
| line 239
Anchor |
description='WaypointInterpolator Example' User-interaction hint for this node. |
<!--
Color-coding legend: X3D terminology
<X3dNode
DEF='idName' field='value'/>
matches XML terminology
<XmlElement
DEF='idName' attribute='value'/>
(Light-blue background: event-based behavior node or statement)
(Grey background inside box: inserted documentation)
(Magenta background: X3D Extensibility)
<ProtoDeclare name='ProtoName'>
<field
name='fieldName'/> </ProtoDeclare>
-->
<!--
For additional help information about X3D scenes, please see X3D Tooltips, X3D Resources, and X3D Scene Authoring Hints.
-->