@prefix :        <https://www.web3d.org/x3d/content/examples/Savage/Tools/Animation/WaypointInterpolatorPrototype.ttl#> .
@prefix owl:     <http://www.w3.org/2002/07/owl#> .
@prefix rdf:     <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
@prefix rdfs:    <http://www.w3.org/2000/01/rdf-schema#> .
@prefix schema:  <http://schema.org/> .
@prefix dcterms: <http://purl.org/dc/terms/> .
@prefix xsd:     <http://www.w3.org/2001/XMLSchema#> .
@prefix x3d:     <https://www.web3d.org/specifications/x3d-4.0.xsd#> .
@prefix x3do:    <https://www.web3d.org/specifications/X3dOntology4.0#> .

:X3D a owl:NamedIndividual, x3do:X3D ;
  x3do:hasHead :head ;
  x3do:hasScene :Scene ;
  x3do:profile 'Immersive' ;
  x3do:version '3.2' ;
  x3do:noNamespaceSchemaLocation 'https://www.web3d.org/specifications/x3d-3.2.xsd' .
:head a owl:NamedIndividual, x3do:head ;
  x3do:hasParent :X3D ;
  x3do:hasMeta :meta_1_1, :meta_1_2, :meta_1_3, :meta_1_4, :meta_1_5, :meta_1_6, :meta_1_7, :meta_1_8, :meta_1_9, :meta_1_10 .
:meta_1_1 a owl:NamedIndividual, x3do:meta ;
  x3do:hasParent :head ;
  x3do:content 'WaypointInterpolatorPrototype.x3d' ;
  x3do:name 'title' .
:meta_1_2 a owl:NamedIndividual, x3do:meta ;
  x3do:hasParent :head ;
  x3do: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.' ;
  x3do:name 'description' .
:meta_1_3 a owl:NamedIndividual, x3do:meta ;
  x3do:hasParent :head ;
  x3do:content 'Don Brutzman, Curtis Blais, Jeff Weekley, Jane Wu' ;
  x3do:name 'creator' .
:meta_1_4 a owl:NamedIndividual, x3do:meta ;
  x3do:hasParent :head ;
  x3do:content '6 April 2001' ;
  x3do:name 'created' .
:meta_1_5 a owl:NamedIndividual, x3do:meta ;
  x3do:hasParent :head ;
  x3do:content '23 August 2023' ;
  x3do:name 'modified' .
:meta_1_6 a owl:NamedIndividual, x3do:meta ;
  x3do:hasParent :head ;
  x3do:content 'https://www.web3d.org/x3d/content/examples/Savage/Tools/Animation/WaypointInterpolatorPrototype.x3d' ;
  x3do:name 'identifier' .
:meta_1_7 a owl:NamedIndividual, x3do:meta ;
  x3do:hasParent :head ;
  x3do:content 'https://www.web3d.org/x3d/content/examples/Savage/Tools/Animation/WaypointInterpolatorExample.x3d' ;
  x3do:name 'reference' .
:meta_1_8 a owl:NamedIndividual, x3do:meta ;
  x3do:hasParent :head ;
  x3do:content 'browsers do not compute pitch angle consistently' ;
  x3do:name 'warning' .
:meta_1_9 a owl:NamedIndividual, x3do:meta ;
  x3do:hasParent :head ;
  x3do:content 'X3D-Edit 4.0, https://www.web3d.org/x3d/tools/X3D-Edit' ;
  x3do:name 'generator' .
:meta_1_10 a owl:NamedIndividual, x3do:meta ;
  x3do:hasParent :head ;
  x3do:content '../../license.html' ;
  x3do:name 'license' .
:meta dcterms:title "WaypointInterpolatorPrototype.x3d" .
:meta dcterms:description "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." .
:meta dcterms:creator "Don Brutzman, Curtis Blais, Jeff Weekley, Jane Wu" .
:meta dcterms:created "6 April 2001" .
:meta dcterms:modified "23 August 2023" .
:meta dcterms:identifier "https://www.web3d.org/x3d/content/examples/Savage/Tools/Animation/WaypointInterpolatorPrototype.x3d" .
:meta dcterms:reference "https://www.web3d.org/x3d/content/examples/Savage/Tools/Animation/WaypointInterpolatorExample.x3d" .
:meta dcterms:warning "browsers do not compute pitch angle consistently" .
:meta dcterms:generator "X3D-Edit 4.0, https://www.web3d.org/x3d/tools/X3D-Edit" .
:meta dcterms:license "../../license.html" .
:Scene a owl:NamedIndividual, x3do:Scene ;
  x3do:hasParent :X3D ;
  x3do:hasChildren :WorldInfo_2_1, :Anchor_2_3 ;
  x3do:hasProtoDeclare :ProtoDeclare_2_2 .
:WorldInfo_2_1 a owl:NamedIndividual, x3do:WorldInfo ;
  x3do:hasParent :Scene ;
  x3do:title 'WaypointInterpolatorPrototype.x3d' .
:ProtoDeclare_2_2 a owl:NamedIndividual, x3do:ProtoDeclare ;
  x3do:hasParent :Scene ;
  x3do:hasProtoInterface :ProtoInterface_2_2_1 ;
  x3do:hasProtoBody :ProtoBody_2_2_2 ;
  x3do:appinfo 'Reads waypoints and legSpeeds/legDurations/defaultSpeed to provide a customizable position/orientation interpolator.' ;
  x3do:name 'WaypointInterpolator' .
:ProtoInterface_2_2_1 a owl:NamedIndividual, x3do:ProtoInterface ;
  x3do:hasParent :ProtoDeclare_2_2 ;
  x3do:hasField :field_2_2_1_1, :field_2_2_1_2, :field_2_2_1_3, :field_2_2_1_4, :field_2_2_1_5, :field_2_2_1_6, :field_2_2_1_7, :field_2_2_1_8, :field_2_2_1_9, :field_2_2_1_10, :field_2_2_1_11, :field_2_2_1_12, :field_2_2_1_13, :field_2_2_1_14, :field_2_2_1_15, :field_2_2_1_16, :field_2_2_1_17, :field_2_2_1_18, :field_2_2_1_19, :field_2_2_1_20, :field_2_2_1_21, :field_2_2_1_22, :field_2_2_1_23, :field_2_2_1_24, :field_2_2_1_25 .
:field_2_2_1_1 a owl:NamedIndividual, x3do:field ;
  x3do:hasParent :ProtoInterface_2_2_1 ;
  x3do:accessType 'initializeOnly' ;
  x3do:appinfo 'Short description of what is animated by this WaypointInterpolator.' ;
  x3do:name 'description' ;
  x3do:type 'SFString' .
:field_2_2_1_2 a owl:NamedIndividual, x3do:field ;
  x3do:hasParent :ProtoInterface_2_2_1 ;
  x3do:accessType 'initializeOnly' ;
  x3do:appinfo 'Waypoints being traversed with interpolation of intermediate positions and orientations.' ;
  x3do:name 'waypoints' ;
  x3do:type 'MFVec3f' ;
  x3do:value ( 0 0 0 0 0 0 ) .
:field_2_2_1_3 a owl:NamedIndividual, x3do:field ;
  x3do:hasParent :ProtoInterface_2_2_1 ;
  x3do:accessType 'inputOnly' ;
  x3do:appinfo 'Add another single waypoint to array of waypoints recalculate interpolator values.' ;
  x3do:name 'add_waypoint' ;
  x3do:type 'SFVec3f' .
:field_2_2_1_4 a owl:NamedIndividual, x3do:field ;
  x3do:hasParent :ProtoInterface_2_2_1 ;
  x3do:accessType 'inputOnly' ;
  x3do:appinfo 'Replace all waypoints recalculate interpolator values.' ;
  x3do:name 'set_waypoints' ;
  x3do:type 'MFVec3f' .
:field_2_2_1_5 a owl:NamedIndividual, x3do:field ;
  x3do:hasParent :ProtoInterface_2_2_1 ;
  x3do:accessType 'initializeOnly' ;
  x3do:appinfo 'Whether to pitch child geometry (such as a vehicle) up or down to match vertical slope' ;
  x3do:name 'pitchUpDownForVerticalWaypoints' ;
  x3do:type 'SFBool' ;
  x3do:value false .
:field_2_2_1_6 a owl:NamedIndividual, x3do:field ;
  x3do:hasParent :ProtoInterface_2_2_1 ;
  x3do:accessType 'initializeOnly' ;
  x3do:appinfo 'Units m/sec. If used, array lengths for legSpeeds and legDurations must be one less than number of waypoints.' ;
  x3do:name 'legSpeeds' ;
  x3do:type 'MFFloat' .
:field_2_2_1_7 a owl:NamedIndividual, x3do:field ;
  x3do:hasParent :ProtoInterface_2_2_1 ;
  x3do:accessType 'initializeOnly' ;
  x3do:appinfo 'Units in seconds. If used, array lengths for legSpeeds and legDurations must be one less than number of waypoints.' ;
  x3do:name 'legDurations' ;
  x3do:type 'MFTime' .
:field_2_2_1_8 a owl:NamedIndividual, x3do:field ;
  x3do:hasParent :ProtoInterface_2_2_1 ;
  x3do:accessType 'initializeOnly' ;
  x3do:appinfo 'Units m/sec.' ;
  x3do:name 'defaultSpeed' ;
  x3do:type 'SFFloat' ;
  x3do:value 1 .
:field_2_2_1_9 a owl:NamedIndividual, x3do:field ;
  x3do:hasParent :ProtoInterface_2_2_1 ;
  x3do:accessType 'initializeOnly' ;
  x3do:appinfo 'turningRate (degrees/second) also determines standoff distance prior to waypoint where turn commences. If 0 turns are instantaneous.' ;
  x3do:name 'turningRate' ;
  x3do:type 'SFFloat' ;
  x3do:value 90 .
:field_2_2_1_10 a owl:NamedIndividual, x3do:field ;
  x3do:hasParent :ProtoInterface_2_2_1 ;
  x3do:accessType 'outputOnly' ;
  x3do:appinfo 'Output calculation summing all leg durations, useful for setting TimeSensor cycleInterval. Units in seconds.' ;
  x3do:name 'totalDuration' ;
  x3do:type 'SFTime' .
:field_2_2_1_11 a owl:NamedIndividual, x3do:field ;
  x3do:hasParent :ProtoInterface_2_2_1 ;
  x3do:accessType 'inputOnly' ;
  x3do:appinfo 'exposed PositionInterpolator and OrientationInterpolator setting' ;
  x3do:name 'set_fraction' ;
  x3do:type 'SFFloat' .
:field_2_2_1_12 a owl:NamedIndividual, x3do:field ;
  x3do:hasParent :ProtoInterface_2_2_1 ;
  x3do:accessType 'outputOnly' ;
  x3do:appinfo 'exposed PositionInterpolator setting' ;
  x3do:name 'position_changed' ;
  x3do:type 'SFVec3f' .
:field_2_2_1_13 a owl:NamedIndividual, x3do:field ;
  x3do:hasParent :ProtoInterface_2_2_1 ;
  x3do:accessType 'outputOnly' ;
  x3do:appinfo 'exposed OrientationInterpolator setting' ;
  x3do:name 'orientation_changed' ;
  x3do:type 'SFRotation' .
:field_2_2_1_14 a owl:NamedIndividual, x3do:field ;
  x3do:hasParent :ProtoInterface_2_2_1 ;
  x3do:accessType 'inputOutput' ;
  x3do:appinfo 'default color for non-active line segments' ;
  x3do:name 'lineColor' ;
  x3do:type 'SFColor' ;
  x3do:value ( 0.6 0.6 0.6 ) .
:field_2_2_1_15 a owl:NamedIndividual, x3do:field ;
  x3do:hasParent :ProtoInterface_2_2_1 ;
  x3do:accessType 'inputOutput' ;
  x3do:appinfo 'active segment highlight color' ;
  x3do:name 'highlightSegmentColor' ;
  x3do:type 'SFColor' ;
  x3do:value ( 0.3 0.3 1 ) .
:field_2_2_1_16 a owl:NamedIndividual, x3do:field ;
  x3do:hasParent :ProtoInterface_2_2_1 ;
  x3do:accessType 'inputOutput' ;
  x3do:appinfo '1.0 is completely transparent, 0.0 is completely opaque.' ;
  x3do:name 'transparency' ;
  x3do:type 'SFFloat' ;
  x3do:value 0 .
:field_2_2_1_17 a owl:NamedIndividual, x3do:field ;
  x3do:hasParent :ProtoInterface_2_2_1 ;
  x3do:accessType 'initializeOnly' ;
  x3do:appinfo 'allowed values: none; waypoints (produce labels at each waypoint); or interpolation (produce single moving label at interpolator time course speed location)' ;
  x3do:name 'labelDisplayMode' ;
  x3do:type 'SFString' ;
  x3do:value 'waypoints' .
:field_2_2_1_18 a owl:NamedIndividual, x3do:field ;
  x3do:hasParent :ProtoInterface_2_2_1 ;
  x3do:accessType 'initializeOnly' ;
  x3do:appinfo 'allowed values: altitude depth (negate Y value) none' ;
  x3do:name 'heightLabel' ;
  x3do:type 'SFString' ;
  x3do:value 'altitude' .
:field_2_2_1_19 a owl:NamedIndividual, x3do:field ;
  x3do:hasParent :ProtoInterface_2_2_1 ;
  x3do:accessType 'initializeOnly' ;
  x3do:appinfo 'heightLabel relative location' ;
  x3do:name 'labelOffset' ;
  x3do:type 'SFVec3f' ;
  x3do:value ( 0 -1 0 ) .
:field_2_2_1_20 a owl:NamedIndividual, x3do:field ;
  x3do:hasParent :ProtoInterface_2_2_1 ;
  x3do:accessType 'initializeOnly' ;
  x3do:appinfo 'heightLabel text size' ;
  x3do:name 'labelFontSize' ;
  x3do:type 'SFFloat' ;
  x3do:value 1 .
:field_2_2_1_21 a owl:NamedIndividual, x3do:field ;
  x3do:hasParent :ProtoInterface_2_2_1 ;
  x3do:accessType 'initializeOnly' ;
  x3do:appinfo 'heightLabel text color' ;
  x3do:name 'labelColor' ;
  x3do:type 'SFColor' ;
  x3do:value ( 0.8 0.8 0.8 ) .
:field_2_2_1_22 a owl:NamedIndividual, x3do:field ;
  x3do:hasParent :ProtoInterface_2_2_1 ;
  x3do:accessType 'initializeOnly' ;
  x3do:appinfo 'enable console output to trace script computations and prototype progress' ;
  x3do:name 'traceEnabled' ;
  x3do:type 'SFBool' ;
  x3do:value false .
:field_2_2_1_23 a owl:NamedIndividual, x3do:field ;
  x3do:hasParent :ProtoInterface_2_2_1 ;
  x3do:accessType 'initializeOnly' ;
  x3do:appinfo 'Output the number of waypoints totalDistance and totalDuration to console upon initialization' ;
  x3do:name 'outputInitializationComputations' ;
  x3do:type 'SFBool' ;
  x3do:value true .
:field_2_2_1_24 a owl:NamedIndividual, x3do:field ;
  x3do:hasParent :ProtoInterface_2_2_1 ;
  x3do:accessType 'inputOutput' ;
  x3do:appinfo 'default color for vertical drop-line segments' ;
  x3do:name 'verticalDropLineColor' ;
  x3do:type 'SFColor' ;
  x3do:value ( 0.4 0.4 0.4 ) .
:field_2_2_1_25 a owl:NamedIndividual, x3do:field ;
  x3do:hasParent :ProtoInterface_2_2_1 ;
  x3do:accessType 'inputOutput' ;
  x3do:appinfo '1.0 is completely transparent, 0.0 is completely opaque.' ;
  x3do:name 'verticalDropLineTransparency' ;
  x3do:type 'SFFloat' ;
  x3do:value 1 .
:ProtoBody_2_2_2 a owl:NamedIndividual, x3do:ProtoBody ;
  x3do:hasParent :ProtoDeclare_2_2 ;
  x3do:hasChildren :Group_2_2_2_1 .
:Group_2_2_2_1 a owl:NamedIndividual, x3do:Group ;
  x3do:hasParent :ProtoBody_2_2_2 ;
  x3do:hasChildren :WaypointPI.instance, :WaypointOI.instance, :CoordinateLabelsAndViewpointsGroup, :WaypointTrackScript, :VerticalDropLineShape, :HighlightShape, :WaypointLineShape, :MovingVehicleLabel ;
  x3do:hasROUTE :ROUTE_2_2_2_1_5, :ROUTE_2_2_2_1_6, :ROUTE_2_2_2_1_8, :ROUTE_2_2_2_1_9, :ROUTE_2_2_2_1_11, :ROUTE_2_2_2_1_13 .
:WaypointPI.instance a owl:NamedIndividual, x3do:PositionInterpolator ;
  x3do:hasParent :Group_2_2_2_1 ;
  x3do:hasIS :IS_2_2_2_1_1_1 ;
  x3do:DEF 'WaypointPI.instance' ;
  x3do:key ( 0 0.5 1 ) ;
  x3do:keyValue ( 0 0 0 1 1 1 2 2 2 ) .
:IS_2_2_2_1_1_1 a owl:NamedIndividual, x3do:IS ;
  x3do:hasParent :WaypointPI.instance ;
  x3do:hasConnect :connect_2_2_2_1_1_1_1, :connect_2_2_2_1_1_1_2 .
:connect_2_2_2_1_1_1_1 a owl:NamedIndividual, x3do:connect ;
  x3do:hasParent :IS_2_2_2_1_1_1 ;
  x3do:nodeField 'set_fraction' ;
  x3do:protoField 'set_fraction' .
:connect_2_2_2_1_1_1_2 a owl:NamedIndividual, x3do:connect ;
  x3do:hasParent :IS_2_2_2_1_1_1 ;
  x3do:nodeField 'value_changed' ;
  x3do:protoField 'position_changed' .
:WaypointOI.instance a owl:NamedIndividual, x3do:OrientationInterpolator ;
  x3do:hasParent :Group_2_2_2_1 ;
  x3do:hasIS :IS_2_2_2_1_2_1 ;
  x3do:DEF 'WaypointOI.instance' .
:IS_2_2_2_1_2_1 a owl:NamedIndividual, x3do:IS ;
  x3do:hasParent :WaypointOI.instance ;
  x3do:hasConnect :connect_2_2_2_1_2_1_1, :connect_2_2_2_1_2_1_2 .
:connect_2_2_2_1_2_1_1 a owl:NamedIndividual, x3do:connect ;
  x3do:hasParent :IS_2_2_2_1_2_1 ;
  x3do:nodeField 'set_fraction' ;
  x3do:protoField 'set_fraction' .
:connect_2_2_2_1_2_1_2 a owl:NamedIndividual, x3do:connect ;
  x3do:hasParent :IS_2_2_2_1_2_1 ;
  x3do:nodeField 'value_changed' ;
  x3do:protoField 'orientation_changed' .
:CoordinateLabelsAndViewpointsGroup a owl:NamedIndividual, x3do:Group ;
  x3do:hasParent :Group_2_2_2_1 ;
  x3do:DEF 'CoordinateLabelsAndViewpointsGroup' .
:WaypointTrackScript a owl:NamedIndividual, x3do:Script ;
  x3do:hasParent :Group_2_2_2_1 ;
  x3do:hasField :field_2_2_2_1_4_1, :field_2_2_2_1_4_2, :field_2_2_2_1_4_3, :field_2_2_2_1_4_4, :field_2_2_2_1_4_5, :field_2_2_2_1_4_6, :field_2_2_2_1_4_7, :field_2_2_2_1_4_8, :field_2_2_2_1_4_9, :field_2_2_2_1_4_10, :field_2_2_2_1_4_11, :field_2_2_2_1_4_12, :field_2_2_2_1_4_13, :field_2_2_2_1_4_14, :field_2_2_2_1_4_15, :field_2_2_2_1_4_16, :field_2_2_2_1_4_17, :field_2_2_2_1_4_18, :field_2_2_2_1_4_19, :field_2_2_2_1_4_20, :field_2_2_2_1_4_21, :field_2_2_2_1_4_22, :field_2_2_2_1_4_23, :field_2_2_2_1_4_24, :field_2_2_2_1_4_25, :field_2_2_2_1_4_26, :field_2_2_2_1_4_27, :field_2_2_2_1_4_28, :field_2_2_2_1_4_29, :field_2_2_2_1_4_30, :field_2_2_2_1_4_31, :field_2_2_2_1_4_32, :field_2_2_2_1_4_33, :field_2_2_2_1_4_34, :field_2_2_2_1_4_35, :field_2_2_2_1_4_36, :field_2_2_2_1_4_37, :field_2_2_2_1_4_38, :field_2_2_2_1_4_39, :field_2_2_2_1_4_40, :field_2_2_2_1_4_41, :field_2_2_2_1_4_42, :field_2_2_2_1_4_43, :field_2_2_2_1_4_44, :field_2_2_2_1_4_45, :field_2_2_2_1_4_46, :field_2_2_2_1_4_47, :field_2_2_2_1_4_48, :field_2_2_2_1_4_49, :field_2_2_2_1_4_50, :field_2_2_2_1_4_51 ;
  x3do:hasIS :IS_2_2_2_1_4_52 ;
  x3do:DEF 'WaypointTrackScript' ;
  x3do:directOutput true ;
  x3do:sourceCode """
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 ();
}
""" .
:field_2_2_2_1_4_1 a owl:NamedIndividual, x3do:field ;
  x3do:hasParent :WaypointTrackScript ;
  x3do:accessType 'initializeOnly' ;
  x3do:name 'description' ;
  x3do:type 'SFString' .
:field_2_2_2_1_4_2 a owl:NamedIndividual, x3do:field ;
  x3do:hasParent :WaypointTrackScript ;
  x3do:accessType 'initializeOnly' ;
  x3do:name 'waypoints' ;
  x3do:type 'MFVec3f' .
:field_2_2_2_1_4_3 a owl:NamedIndividual, x3do:field ;
  x3do:hasParent :WaypointTrackScript ;
  x3do:accessType 'inputOnly' ;
  x3do:name 'add_waypoint' ;
  x3do:type 'SFVec3f' .
:field_2_2_2_1_4_4 a owl:NamedIndividual, x3do:field ;
  x3do:hasParent :WaypointTrackScript ;
  x3do:accessType 'inputOnly' ;
  x3do:name 'set_waypoints' ;
  x3do:type 'MFVec3f' .
:field_2_2_2_1_4_5 a owl:NamedIndividual, x3do:field ;
  x3do:hasParent :WaypointTrackScript ;
  x3do:accessType 'initializeOnly' ;
  x3do:name 'pitchUpDownForVerticalWaypoints' ;
  x3do:type 'SFBool' .
:field_2_2_2_1_4_6 a owl:NamedIndividual, x3do:field ;
  x3do:hasParent :WaypointTrackScript ;
  x3do:accessType 'initializeOnly' ;
  x3do:name 'legSpeeds' ;
  x3do:type 'MFFloat' .
:field_2_2_2_1_4_7 a owl:NamedIndividual, x3do:field ;
  x3do:hasParent :WaypointTrackScript ;
  x3do:accessType 'initializeOnly' ;
  x3do:name 'legDurations' ;
  x3do:type 'MFTime' .
:field_2_2_2_1_4_8 a owl:NamedIndividual, x3do:field ;
  x3do:hasParent :WaypointTrackScript ;
  x3do:accessType 'initializeOnly' ;
  x3do:name 'defaultSpeed' ;
  x3do:type 'SFFloat' .
:field_2_2_2_1_4_9 a owl:NamedIndividual, x3do:field ;
  x3do:hasParent :WaypointTrackScript ;
  x3do:accessType 'initializeOnly' ;
  x3do:name 'turningRate' ;
  x3do:type 'SFFloat' .
:field_2_2_2_1_4_10 a owl:NamedIndividual, x3do:field ;
  x3do:hasParent :WaypointTrackScript ;
  x3do:accessType 'outputOnly' ;
  x3do:name 'totalDuration' ;
  x3do:type 'SFTime' .
:field_2_2_2_1_4_11 a owl:NamedIndividual, x3do:field ;
  x3do:hasParent :WaypointTrackScript ;
  x3do:hasChildren :WaypointPI.instance-USE-1 ;
  x3do:accessType 'initializeOnly' ;
  x3do:name 'WaypointPI' ;
  x3do:type 'SFNode' .
:WaypointPI.instance-USE-1 a owl:NamedIndividual, x3do:PositionInterpolator ;
  x3do:hasParent :field_2_2_2_1_4_11 ;
  owl:sameAs :WaypointPI.instance . # DEF matching this USE
:field_2_2_2_1_4_12 a owl:NamedIndividual, x3do:field ;
  x3do:hasParent :WaypointTrackScript ;
  x3do:hasChildren :WaypointOI.instance-USE-1 ;
  x3do:accessType 'initializeOnly' ;
  x3do:name 'WaypointOI' ;
  x3do:type 'SFNode' .
:WaypointOI.instance-USE-1 a owl:NamedIndividual, x3do:OrientationInterpolator ;
  x3do:hasParent :field_2_2_2_1_4_12 ;
  owl:sameAs :WaypointOI.instance . # DEF matching this USE
:field_2_2_2_1_4_13 a owl:NamedIndividual, x3do:field ;
  x3do:hasParent :WaypointTrackScript ;
  x3do:accessType 'outputOnly' ;
  x3do:name 'pointIndices' ;
  x3do:type 'MFInt32' .
:field_2_2_2_1_4_14 a owl:NamedIndividual, x3do:field ;
  x3do:hasParent :WaypointTrackScript ;
  x3do:hasChildren :CoordinateLabelsAndViewpointsGroup-USE-1 ;
  x3do:accessType 'initializeOnly' ;
  x3do:name 'OutputLabelsGroup' ;
  x3do:type 'SFNode' .
:CoordinateLabelsAndViewpointsGroup-USE-1 a owl:NamedIndividual, x3do:Group ;
  x3do:hasParent :field_2_2_2_1_4_14 ;
  owl:sameAs :CoordinateLabelsAndViewpointsGroup . # DEF matching this USE
:field_2_2_2_1_4_15 a owl:NamedIndividual, x3do:field ;
  x3do:hasParent :WaypointTrackScript ;
  x3do:accessType 'inputOnly' ;
  x3do:name 'set_fraction' ;
  x3do:type 'SFFloat' .
:field_2_2_2_1_4_16 a owl:NamedIndividual, x3do:field ;
  x3do:hasParent :WaypointTrackScript ;
  x3do:accessType 'outputOnly' ;
  x3do:appinfo 'Initialized to (0 0 0 0 0 0)' ;
  x3do:name 'highlightCoordinates' ;
  x3do:type 'MFVec3f' .
:field_2_2_2_1_4_17 a owl:NamedIndividual, x3do:field ;
  x3do:hasParent :WaypointTrackScript ;
  x3do:accessType 'initializeOnly' ;
  x3do:name 'heightLabel' ;
  x3do:type 'SFString' .
:field_2_2_2_1_4_18 a owl:NamedIndividual, x3do:field ;
  x3do:hasParent :WaypointTrackScript ;
  x3do:accessType 'initializeOnly' ;
  x3do:name 'labelDisplayMode' ;
  x3do:type 'SFString' .
:field_2_2_2_1_4_19 a owl:NamedIndividual, x3do:field ;
  x3do:hasParent :WaypointTrackScript ;
  x3do:accessType 'initializeOnly' ;
  x3do:name 'labelOffset' ;
  x3do:type 'SFVec3f' .
:field_2_2_2_1_4_20 a owl:NamedIndividual, x3do:field ;
  x3do:hasParent :WaypointTrackScript ;
  x3do:accessType 'initializeOnly' ;
  x3do:name 'labelFontSize' ;
  x3do:type 'SFFloat' .
:field_2_2_2_1_4_21 a owl:NamedIndividual, x3do:field ;
  x3do:hasParent :WaypointTrackScript ;
  x3do:accessType 'initializeOnly' ;
  x3do:name 'labelColor' ;
  x3do:type 'SFColor' .
:field_2_2_2_1_4_22 a owl:NamedIndividual, x3do:field ;
  x3do:hasParent :WaypointTrackScript ;
  x3do:accessType 'outputOnly' ;
  x3do:name 'labelInterpolation' ;
  x3do:type 'MFString' .
:field_2_2_2_1_4_23 a owl:NamedIndividual, x3do:field ;
  x3do:hasParent :WaypointTrackScript ;
  x3do:accessType 'initializeOnly' ;
  x3do:name 'traceEnabled' ;
  x3do:type 'SFBool' .
:field_2_2_2_1_4_24 a owl:NamedIndividual, x3do:field ;
  x3do:hasParent :WaypointTrackScript ;
  x3do:accessType 'initializeOnly' ;
  x3do:appinfo 'Output the number of waypoints totalDistance and totalDuration to console upon initialization' ;
  x3do:name 'outputInitializationComputations' ;
  x3do:type 'SFBool' .
:field_2_2_2_1_4_25 a owl:NamedIndividual, x3do:field ;
  x3do:hasParent :WaypointTrackScript ;
  x3do:accessType 'initializeOnly' ;
  x3do:appinfo 'whether or not an error was detected during script processing.' ;
  x3do:name 'scriptError' ;
  x3do:type 'SFBool' ;
  x3do:value false .
:field_2_2_2_1_4_26 a owl:NamedIndividual, x3do:field ;
  x3do:hasParent :WaypointTrackScript ;
  x3do:accessType 'initializeOnly' ;
  x3do:appinfo 'retain state information while constructing fraction array' ;
  x3do:name 'previousFractionIndex' ;
  x3do:type 'SFInt32' ;
  x3do:value 0 .
:field_2_2_2_1_4_27 a owl:NamedIndividual, x3do:field ;
  x3do:hasParent :WaypointTrackScript ;
  x3do:accessType 'initializeOnly' ;
  x3do:appinfo 'label' ;
  x3do:name 'depthString' ;
  x3do:type 'SFString' .
:field_2_2_2_1_4_28 a owl:NamedIndividual, x3do:field ;
  x3do:hasParent :WaypointTrackScript ;
  x3do:accessType 'initializeOnly' ;
  x3do:appinfo 'label' ;
  x3do:name 'whichRotationVersion' ;
  x3do:type 'SFString' .
:field_2_2_2_1_4_29 a owl:NamedIndividual, x3do:field ;
  x3do:hasParent :WaypointTrackScript ;
  x3do:accessType 'outputOnly' ;
  x3do:name 'verticalDropLineIndices' ;
  x3do:type 'MFInt32' .
:field_2_2_2_1_4_30 a owl:NamedIndividual, x3do:field ;
  x3do:hasParent :WaypointTrackScript ;
  x3do:accessType 'outputOnly' ;
  x3do:name 'verticalDropLinePoints' ;
  x3do:type 'MFVec3f' .
:field_2_2_2_1_4_31 a owl:NamedIndividual, x3do:field ;
  x3do:hasParent :WaypointTrackScript ;
  x3do:accessType 'initializeOnly' ;
  x3do:name 'positionKey' ;
  x3do:type 'MFFloat' ;
  x3do:value ( 0 ) .
:field_2_2_2_1_4_32 a owl:NamedIndividual, x3do:field ;
  x3do:hasParent :WaypointTrackScript ;
  x3do:accessType 'initializeOnly' ;
  x3do:name 'positionKeyValueArray' ;
  x3do:type 'MFVec3f' .
:field_2_2_2_1_4_33 a owl:NamedIndividual, x3do:field ;
  x3do:hasParent :WaypointTrackScript ;
  x3do:accessType 'outputOnly' ;
  x3do:name 'finalPositionKey' ;
  x3do:type 'MFFloat' .
:field_2_2_2_1_4_34 a owl:NamedIndividual, x3do:field ;
  x3do:hasParent :WaypointTrackScript ;
  x3do:accessType 'outputOnly' ;
  x3do:name 'finalPositionKeyValueArray' ;
  x3do:type 'MFVec3f' .
:field_2_2_2_1_4_35 a owl:NamedIndividual, x3do:field ;
  x3do:hasParent :WaypointTrackScript ;
  x3do:accessType 'initializeOnly' ;
  x3do:name 'distances' ;
  x3do:type 'MFFloat' .
:field_2_2_2_1_4_36 a owl:NamedIndividual, x3do:field ;
  x3do:hasParent :WaypointTrackScript ;
  x3do:accessType 'initializeOnly' ;
  x3do:name 'pointIndicesAccumulator' ;
  x3do:type 'MFInt32' .
:field_2_2_2_1_4_37 a owl:NamedIndividual, x3do:field ;
  x3do:hasParent :WaypointTrackScript ;
  x3do:accessType 'initializeOnly' ;
  x3do:name 'verticalDropLineIndicesAccumulator' ;
  x3do:type 'MFInt32' .
:field_2_2_2_1_4_38 a owl:NamedIndividual, x3do:field ;
  x3do:hasParent :WaypointTrackScript ;
  x3do:accessType 'initializeOnly' ;
  x3do:name 'verticalDropLinePointsAccumulator' ;
  x3do:type 'MFVec3f' .
:field_2_2_2_1_4_39 a owl:NamedIndividual, x3do:field ;
  x3do:hasParent :WaypointTrackScript ;
  x3do:accessType 'initializeOnly' ;
  x3do:name 'totalDistance' ;
  x3do:type 'SFFloat' ;
  x3do:value 0 .
:field_2_2_2_1_4_40 a owl:NamedIndividual, x3do:field ;
  x3do:hasParent :WaypointTrackScript ;
  x3do:accessType 'initializeOnly' ;
  x3do:name 'orientations' ;
  x3do:type 'MFRotation' .
:field_2_2_2_1_4_41 a owl:NamedIndividual, x3do:field ;
  x3do:hasParent :WaypointTrackScript ;
  x3do:accessType 'initializeOnly' ;
  x3do:name 'dx' ;
  x3do:type 'SFFloat' ;
  x3do:value 0 .
:field_2_2_2_1_4_42 a owl:NamedIndividual, x3do:field ;
  x3do:hasParent :WaypointTrackScript ;
  x3do:accessType 'initializeOnly' ;
  x3do:name 'dy' ;
  x3do:type 'SFFloat' ;
  x3do:value 0 .
:field_2_2_2_1_4_43 a owl:NamedIndividual, x3do:field ;
  x3do:hasParent :WaypointTrackScript ;
  x3do:accessType 'initializeOnly' ;
  x3do:name 'dz' ;
  x3do:type 'SFFloat' ;
  x3do:value 0 .
:field_2_2_2_1_4_44 a owl:NamedIndividual, x3do:field ;
  x3do:hasParent :WaypointTrackScript ;
  x3do:accessType 'initializeOnly' ;
  x3do:name 'legDistance' ;
  x3do:type 'SFFloat' ;
  x3do:value 0 .
:field_2_2_2_1_4_45 a owl:NamedIndividual, x3do:field ;
  x3do:hasParent :WaypointTrackScript ;
  x3do:accessType 'initializeOnly' ;
  x3do:name 'heading' ;
  x3do:type 'SFFloat' ;
  x3do:value 0 .
:field_2_2_2_1_4_46 a owl:NamedIndividual, x3do:field ;
  x3do:hasParent :WaypointTrackScript ;
  x3do:accessType 'initializeOnly' ;
  x3do:name 'pitchAngle' ;
  x3do:type 'SFFloat' ;
  x3do:value 0 .
:field_2_2_2_1_4_47 a owl:NamedIndividual, x3do:field ;
  x3do:hasParent :WaypointTrackScript ;
  x3do:accessType 'initializeOnly' ;
  x3do:name 'orientationKey' ;
  x3do:type 'MFFloat' .
:field_2_2_2_1_4_48 a owl:NamedIndividual, x3do:field ;
  x3do:hasParent :WaypointTrackScript ;
  x3do:accessType 'initializeOnly' ;
  x3do:name 'newKey' ;
  x3do:type 'MFFloat' .
:field_2_2_2_1_4_49 a owl:NamedIndividual, x3do:field ;
  x3do:hasParent :WaypointTrackScript ;
  x3do:accessType 'initializeOnly' ;
  x3do:name 'newKeyValue' ;
  x3do:type 'MFRotation' .
:field_2_2_2_1_4_50 a owl:NamedIndividual, x3do:field ;
  x3do:hasParent :WaypointTrackScript ;
  x3do:accessType 'initializeOnly' ;
  x3do:name 'outputChild' ;
  x3do:type 'MFNode' .
:field_2_2_2_1_4_51 a owl:NamedIndividual, x3do:field ;
  x3do:hasParent :WaypointTrackScript ;
  x3do:accessType 'initializeOnly' ;
  x3do:name 'rotatedVector' ;
  x3do:type 'SFVec3f' ;
  x3do:value ( 0 0 0 ) .
:IS_2_2_2_1_4_52 a owl:NamedIndividual, x3do:IS ;
  x3do:hasParent :WaypointTrackScript ;
  x3do:hasConnect :connect_2_2_2_1_4_52_1, :connect_2_2_2_1_4_52_2, :connect_2_2_2_1_4_52_3, :connect_2_2_2_1_4_52_4, :connect_2_2_2_1_4_52_5, :connect_2_2_2_1_4_52_6, :connect_2_2_2_1_4_52_7, :connect_2_2_2_1_4_52_8, :connect_2_2_2_1_4_52_9, :connect_2_2_2_1_4_52_10, :connect_2_2_2_1_4_52_11, :connect_2_2_2_1_4_52_12, :connect_2_2_2_1_4_52_13, :connect_2_2_2_1_4_52_14, :connect_2_2_2_1_4_52_15, :connect_2_2_2_1_4_52_16, :connect_2_2_2_1_4_52_17, :connect_2_2_2_1_4_52_18 .
:connect_2_2_2_1_4_52_1 a owl:NamedIndividual, x3do:connect ;
  x3do:hasParent :IS_2_2_2_1_4_52 ;
  x3do:nodeField 'description' ;
  x3do:protoField 'description' .
:connect_2_2_2_1_4_52_2 a owl:NamedIndividual, x3do:connect ;
  x3do:hasParent :IS_2_2_2_1_4_52 ;
  x3do:nodeField 'waypoints' ;
  x3do:protoField 'waypoints' .
:connect_2_2_2_1_4_52_3 a owl:NamedIndividual, x3do:connect ;
  x3do:hasParent :IS_2_2_2_1_4_52 ;
  x3do:nodeField 'add_waypoint' ;
  x3do:protoField 'add_waypoint' .
:connect_2_2_2_1_4_52_4 a owl:NamedIndividual, x3do:connect ;
  x3do:hasParent :IS_2_2_2_1_4_52 ;
  x3do:nodeField 'set_waypoints' ;
  x3do:protoField 'set_waypoints' .
:connect_2_2_2_1_4_52_5 a owl:NamedIndividual, x3do:connect ;
  x3do:hasParent :IS_2_2_2_1_4_52 ;
  x3do:nodeField 'pitchUpDownForVerticalWaypoints' ;
  x3do:protoField 'pitchUpDownForVerticalWaypoints' .
:connect_2_2_2_1_4_52_6 a owl:NamedIndividual, x3do:connect ;
  x3do:hasParent :IS_2_2_2_1_4_52 ;
  x3do:nodeField 'legSpeeds' ;
  x3do:protoField 'legSpeeds' .
:connect_2_2_2_1_4_52_7 a owl:NamedIndividual, x3do:connect ;
  x3do:hasParent :IS_2_2_2_1_4_52 ;
  x3do:nodeField 'legDurations' ;
  x3do:protoField 'legDurations' .
:connect_2_2_2_1_4_52_8 a owl:NamedIndividual, x3do:connect ;
  x3do:hasParent :IS_2_2_2_1_4_52 ;
  x3do:nodeField 'defaultSpeed' ;
  x3do:protoField 'defaultSpeed' .
:connect_2_2_2_1_4_52_9 a owl:NamedIndividual, x3do:connect ;
  x3do:hasParent :IS_2_2_2_1_4_52 ;
  x3do:nodeField 'turningRate' ;
  x3do:protoField 'turningRate' .
:connect_2_2_2_1_4_52_10 a owl:NamedIndividual, x3do:connect ;
  x3do:hasParent :IS_2_2_2_1_4_52 ;
  x3do:nodeField 'totalDuration' ;
  x3do:protoField 'totalDuration' .
:connect_2_2_2_1_4_52_11 a owl:NamedIndividual, x3do:connect ;
  x3do:hasParent :IS_2_2_2_1_4_52 ;
  x3do:nodeField 'set_fraction' ;
  x3do:protoField 'set_fraction' .
:connect_2_2_2_1_4_52_12 a owl:NamedIndividual, x3do:connect ;
  x3do:hasParent :IS_2_2_2_1_4_52 ;
  x3do:nodeField 'heightLabel' ;
  x3do:protoField 'heightLabel' .
:connect_2_2_2_1_4_52_13 a owl:NamedIndividual, x3do:connect ;
  x3do:hasParent :IS_2_2_2_1_4_52 ;
  x3do:nodeField 'labelDisplayMode' ;
  x3do:protoField 'labelDisplayMode' .
:connect_2_2_2_1_4_52_14 a owl:NamedIndividual, x3do:connect ;
  x3do:hasParent :IS_2_2_2_1_4_52 ;
  x3do:nodeField 'labelOffset' ;
  x3do:protoField 'labelOffset' .
:connect_2_2_2_1_4_52_15 a owl:NamedIndividual, x3do:connect ;
  x3do:hasParent :IS_2_2_2_1_4_52 ;
  x3do:nodeField 'labelFontSize' ;
  x3do:protoField 'labelFontSize' .
:connect_2_2_2_1_4_52_16 a owl:NamedIndividual, x3do:connect ;
  x3do:hasParent :IS_2_2_2_1_4_52 ;
  x3do:nodeField 'labelColor' ;
  x3do:protoField 'labelColor' .
:connect_2_2_2_1_4_52_17 a owl:NamedIndividual, x3do:connect ;
  x3do:hasParent :IS_2_2_2_1_4_52 ;
  x3do:nodeField 'traceEnabled' ;
  x3do:protoField 'traceEnabled' .
:connect_2_2_2_1_4_52_18 a owl:NamedIndividual, x3do:connect ;
  x3do:hasParent :IS_2_2_2_1_4_52 ;
  x3do:nodeField 'outputInitializationComputations' ;
  x3do:protoField 'outputInitializationComputations' .
:ROUTE_2_2_2_1_5 a owl:NamedIndividual, x3do:ROUTE ;
  x3do:hasParent :Group_2_2_2_1 ;
  x3do:fromField 'finalPositionKey' ;
  x3do:fromNode 'WaypointTrackScript' ;
  x3do:toField 'key' ;
  x3do:toNode 'WaypointPI.instance' .
:ROUTE_2_2_2_1_6 a owl:NamedIndividual, x3do:ROUTE ;
  x3do:hasParent :Group_2_2_2_1 ;
  x3do:fromField 'finalPositionKeyValueArray' ;
  x3do:fromNode 'WaypointTrackScript' ;
  x3do:toField 'keyValue' ;
  x3do:toNode 'WaypointPI.instance' .
:VerticalDropLineShape a owl:NamedIndividual, x3do:Shape ;
  x3do:hasParent :Group_2_2_2_1 ;
  x3do:hasGeometry :VerticalDropLine ;
  x3do:hasAppearance :Appearance_2_2_2_1_7_2 ;
  x3do:DEF 'VerticalDropLineShape' .
:VerticalDropLine a owl:NamedIndividual, x3do:IndexedLineSet ;
  x3do:hasParent :VerticalDropLineShape ;
  x3do:hasCoord :VerticalDropLineCoordinates ;
  x3do:DEF 'VerticalDropLine' .
:VerticalDropLineCoordinates a owl:NamedIndividual, x3do:Coordinate ;
  x3do:hasParent :VerticalDropLine ;
  x3do:DEF 'VerticalDropLineCoordinates' .
:Appearance_2_2_2_1_7_2 a owl:NamedIndividual, x3do:Appearance ;
  x3do:hasParent :VerticalDropLineShape ;
  x3do:hasMaterial :VerticalDropLineMaterial .
:VerticalDropLineMaterial a owl:NamedIndividual, x3do:Material ;
  x3do:hasParent :Appearance_2_2_2_1_7_2 ;
  x3do:hasIS :IS_2_2_2_1_7_2_1_1 ;
  x3do:DEF 'VerticalDropLineMaterial' .
:IS_2_2_2_1_7_2_1_1 a owl:NamedIndividual, x3do:IS ;
  x3do:hasParent :VerticalDropLineMaterial ;
  x3do:hasConnect :connect_2_2_2_1_7_2_1_1_1, :connect_2_2_2_1_7_2_1_1_2 .
:connect_2_2_2_1_7_2_1_1_1 a owl:NamedIndividual, x3do:connect ;
  x3do:hasParent :IS_2_2_2_1_7_2_1_1 ;
  x3do:nodeField 'emissiveColor' ;
  x3do:protoField 'verticalDropLineColor' .
:connect_2_2_2_1_7_2_1_1_2 a owl:NamedIndividual, x3do:connect ;
  x3do:hasParent :IS_2_2_2_1_7_2_1_1 ;
  x3do:nodeField 'transparency' ;
  x3do:protoField 'verticalDropLineTransparency' .
:ROUTE_2_2_2_1_8 a owl:NamedIndividual, x3do:ROUTE ;
  x3do:hasParent :Group_2_2_2_1 ;
  x3do:fromField 'verticalDropLineIndices' ;
  x3do:fromNode 'WaypointTrackScript' ;
  x3do:toField 'set_coordIndex' ;
  x3do:toNode 'VerticalDropLine' .
:ROUTE_2_2_2_1_9 a owl:NamedIndividual, x3do:ROUTE ;
  x3do:hasParent :Group_2_2_2_1 ;
  x3do:fromField 'verticalDropLinePoints' ;
  x3do:fromNode 'WaypointTrackScript' ;
  x3do:toField 'point' ;
  x3do:toNode 'VerticalDropLineCoordinates' .
:HighlightShape a owl:NamedIndividual, x3do:Shape ;
  x3do:hasParent :Group_2_2_2_1 ;
  x3do:hasGeometry :HighlightSegment ;
  x3do:hasAppearance :Appearance_2_2_2_1_10_2 ;
  x3do:DEF 'HighlightShape' .
:HighlightSegment a owl:NamedIndividual, x3do:IndexedLineSet ;
  x3do:hasParent :HighlightShape ;
  x3do:hasCoord :HighlightSegmentCoordinates ;
  x3do:DEF 'HighlightSegment' ;
  x3do:coordIndex ( 0 1 -1 ) .
:HighlightSegmentCoordinates a owl:NamedIndividual, x3do:Coordinate ;
  x3do:hasParent :HighlightSegment ;
  x3do:DEF 'HighlightSegmentCoordinates' ;
  x3do:point ( 0 0 0 0 0 0 ) .
:Appearance_2_2_2_1_10_2 a owl:NamedIndividual, x3do:Appearance ;
  x3do:hasParent :HighlightShape ;
  x3do:hasMaterial :HighlightSegmentMaterial .
:HighlightSegmentMaterial a owl:NamedIndividual, x3do:Material ;
  x3do:hasParent :Appearance_2_2_2_1_10_2 ;
  x3do:hasIS :IS_2_2_2_1_10_2_1_1 ;
  x3do:DEF 'HighlightSegmentMaterial' ;
  x3do:diffuseColor ( 0 0 0 ) ;
  x3do:emissiveColor ( 0.2 0.2 0.2 ) .
:IS_2_2_2_1_10_2_1_1 a owl:NamedIndividual, x3do:IS ;
  x3do:hasParent :HighlightSegmentMaterial ;
  x3do:hasConnect :connect_2_2_2_1_10_2_1_1_1, :connect_2_2_2_1_10_2_1_1_2 .
:connect_2_2_2_1_10_2_1_1_1 a owl:NamedIndividual, x3do:connect ;
  x3do:hasParent :IS_2_2_2_1_10_2_1_1 ;
  x3do:nodeField 'emissiveColor' ;
  x3do:protoField 'highlightSegmentColor' .
:connect_2_2_2_1_10_2_1_1_2 a owl:NamedIndividual, x3do:connect ;
  x3do:hasParent :IS_2_2_2_1_10_2_1_1 ;
  x3do:nodeField 'transparency' ;
  x3do:protoField 'transparency' .
:ROUTE_2_2_2_1_11 a owl:NamedIndividual, x3do:ROUTE ;
  x3do:hasParent :Group_2_2_2_1 ;
  x3do:fromField 'highlightCoordinates' ;
  x3do:fromNode 'WaypointTrackScript' ;
  x3do:toField 'point' ;
  x3do:toNode 'HighlightSegmentCoordinates' .
:WaypointLineShape a owl:NamedIndividual, x3do:Shape ;
  x3do:hasParent :Group_2_2_2_1 ;
  x3do:hasGeometry :WaypointLine ;
  x3do:hasAppearance :Appearance_2_2_2_1_12_2 ;
  x3do:DEF 'WaypointLineShape' .
:WaypointLine a owl:NamedIndividual, x3do:IndexedLineSet ;
  x3do:hasParent :WaypointLineShape ;
  x3do:hasCoord :WaypointLineCoordinates ;
  x3do:DEF 'WaypointLine' .
:WaypointLineCoordinates a owl:NamedIndividual, x3do:Coordinate ;
  x3do:hasParent :WaypointLine ;
  x3do:hasIS :IS_2_2_2_1_12_1_1_1 ;
  x3do:DEF 'WaypointLineCoordinates' .
:IS_2_2_2_1_12_1_1_1 a owl:NamedIndividual, x3do:IS ;
  x3do:hasParent :WaypointLineCoordinates ;
  x3do:hasConnect :connect_2_2_2_1_12_1_1_1_1 .
:connect_2_2_2_1_12_1_1_1_1 a owl:NamedIndividual, x3do:connect ;
  x3do:hasParent :IS_2_2_2_1_12_1_1_1 ;
  x3do:nodeField 'point' ;
  x3do:protoField 'waypoints' .
:Appearance_2_2_2_1_12_2 a owl:NamedIndividual, x3do:Appearance ;
  x3do:hasParent :WaypointLineShape ;
  x3do:hasMaterial :WaypointTrackMaterial .
:WaypointTrackMaterial a owl:NamedIndividual, x3do:Material ;
  x3do:hasParent :Appearance_2_2_2_1_12_2 ;
  x3do:hasIS :IS_2_2_2_1_12_2_1_1 ;
  x3do:DEF 'WaypointTrackMaterial' ;
  x3do:emissiveColor ( 0.8 0.8 0.8 ) .
:IS_2_2_2_1_12_2_1_1 a owl:NamedIndividual, x3do:IS ;
  x3do:hasParent :WaypointTrackMaterial ;
  x3do:hasConnect :connect_2_2_2_1_12_2_1_1_1, :connect_2_2_2_1_12_2_1_1_2 .
:connect_2_2_2_1_12_2_1_1_1 a owl:NamedIndividual, x3do:connect ;
  x3do:hasParent :IS_2_2_2_1_12_2_1_1 ;
  x3do:nodeField 'emissiveColor' ;
  x3do:protoField 'lineColor' .
:connect_2_2_2_1_12_2_1_1_2 a owl:NamedIndividual, x3do:connect ;
  x3do:hasParent :IS_2_2_2_1_12_2_1_1 ;
  x3do:nodeField 'transparency' ;
  x3do:protoField 'transparency' .
:ROUTE_2_2_2_1_13 a owl:NamedIndividual, x3do:ROUTE ;
  x3do:hasParent :Group_2_2_2_1 ;
  x3do:fromField 'pointIndices' ;
  x3do:fromNode 'WaypointTrackScript' ;
  x3do:toField 'set_coordIndex' ;
  x3do:toNode 'WaypointLine' .
:MovingVehicleLabel a owl:NamedIndividual, x3do:Transform ;
  x3do:hasParent :Group_2_2_2_1 ;
  x3do:hasROUTE :ROUTE_2_2_2_1_14_1, :ROUTE_2_2_2_1_14_2 ;
  x3do:hasChildren :MovingVehicleLabelOffset ;
  x3do:DEF 'MovingVehicleLabel' .
:ROUTE_2_2_2_1_14_1 a owl:NamedIndividual, x3do:ROUTE ;
  x3do:hasParent :MovingVehicleLabel ;
  x3do:fromField 'value_changed' ;
  x3do:fromNode 'WaypointPI.instance' ;
  x3do:toField 'translation' ;
  x3do:toNode 'MovingVehicleLabel' .
:ROUTE_2_2_2_1_14_2 a owl:NamedIndividual, x3do:ROUTE ;
  x3do:hasParent :MovingVehicleLabel ;
  x3do:fromField 'value_changed' ;
  x3do:fromNode 'WaypointOI.instance' ;
  x3do:toField 'rotation' ;
  x3do:toNode 'MovingVehicleLabel' .
:MovingVehicleLabelOffset a owl:NamedIndividual, x3do:Transform ;
  x3do:hasParent :MovingVehicleLabel ;
  x3do:hasIS :IS_2_2_2_1_14_3_1 ;
  x3do:hasChildren :Billboard_2_2_2_1_14_3_2 ;
  x3do:DEF 'MovingVehicleLabelOffset' .
:IS_2_2_2_1_14_3_1 a owl:NamedIndividual, x3do:IS ;
  x3do:hasParent :MovingVehicleLabelOffset ;
  x3do:hasConnect :connect_2_2_2_1_14_3_1_1 .
:connect_2_2_2_1_14_3_1_1 a owl:NamedIndividual, x3do:connect ;
  x3do:hasParent :IS_2_2_2_1_14_3_1 ;
  x3do:nodeField 'translation' ;
  x3do:protoField 'labelOffset' .
:Billboard_2_2_2_1_14_3_2 a owl:NamedIndividual, x3do:Billboard ;
  x3do:hasParent :MovingVehicleLabelOffset ;
  x3do:hasChildren :Shape_2_2_2_1_14_3_2_1 ;
  x3do:hasROUTE :ROUTE_2_2_2_1_14_3_2_2 .
:Shape_2_2_2_1_14_3_2_1 a owl:NamedIndividual, x3do:Shape ;
  x3do:hasParent :Billboard_2_2_2_1_14_3_2 ;
  x3do:hasGeometry :MovingVehicleLabelText ;
  x3do:hasAppearance :Appearance_2_2_2_1_14_3_2_1_2 .
:MovingVehicleLabelText a owl:NamedIndividual, x3do:Text ;
  x3do:hasParent :Shape_2_2_2_1_14_3_2_1 ;
  x3do:hasFontStyle :MovingVehicleLabelFont ;
  x3do:DEF 'MovingVehicleLabelText' .
:MovingVehicleLabelFont a owl:NamedIndividual, x3do:FontStyle ;
  x3do:hasParent :MovingVehicleLabelText ;
  x3do:hasIS :IS_2_2_2_1_14_3_2_1_1_1_1 ;
  x3do:DEF 'MovingVehicleLabelFont' ;
  x3do:justify '"MIDDLE" "MIDDLE"' .
:IS_2_2_2_1_14_3_2_1_1_1_1 a owl:NamedIndividual, x3do:IS ;
  x3do:hasParent :MovingVehicleLabelFont ;
  x3do:hasConnect :connect_2_2_2_1_14_3_2_1_1_1_1_1 .
:connect_2_2_2_1_14_3_2_1_1_1_1_1 a owl:NamedIndividual, x3do:connect ;
  x3do:hasParent :IS_2_2_2_1_14_3_2_1_1_1_1 ;
  x3do:nodeField 'size' ;
  x3do:protoField 'labelFontSize' .
:Appearance_2_2_2_1_14_3_2_1_2 a owl:NamedIndividual, x3do:Appearance ;
  x3do:hasParent :Shape_2_2_2_1_14_3_2_1 ;
  x3do:hasMaterial :MovingVehicleLabelMaterial .
:MovingVehicleLabelMaterial a owl:NamedIndividual, x3do:Material ;
  x3do:hasParent :Appearance_2_2_2_1_14_3_2_1_2 ;
  x3do:hasIS :IS_2_2_2_1_14_3_2_1_2_1_1 ;
  x3do:DEF 'MovingVehicleLabelMaterial' .
:IS_2_2_2_1_14_3_2_1_2_1_1 a owl:NamedIndividual, x3do:IS ;
  x3do:hasParent :MovingVehicleLabelMaterial ;
  x3do:hasConnect :connect_2_2_2_1_14_3_2_1_2_1_1_1 .
:connect_2_2_2_1_14_3_2_1_2_1_1_1 a owl:NamedIndividual, x3do:connect ;
  x3do:hasParent :IS_2_2_2_1_14_3_2_1_2_1_1 ;
  x3do:nodeField 'diffuseColor' ;
  x3do:protoField 'labelColor' .
:ROUTE_2_2_2_1_14_3_2_2 a owl:NamedIndividual, x3do:ROUTE ;
  x3do:hasParent :Billboard_2_2_2_1_14_3_2 ;
  x3do:fromField 'labelInterpolation' ;
  x3do:fromNode 'WaypointTrackScript' ;
  x3do:toField 'string' ;
  x3do:toNode 'MovingVehicleLabelText' .
:Anchor_2_3 a owl:NamedIndividual, x3do:Anchor ;
  x3do:hasParent :Scene ;
  x3do:hasChildren :Shape_2_3_1, :Shape_2_3_2 ;
  x3do:description 'WaypointInterpolator Example' ;
  x3do: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"' .
:Shape_2_3_1 a owl:NamedIndividual, x3do:Shape ;
  x3do:hasParent :Anchor_2_3 ;
  x3do:hasGeometry :Text_2_3_1_1 ;
  x3do:hasAppearance :Appearance_2_3_1_2 .
:Text_2_3_1_1 a owl:NamedIndividual, x3do:Text ;
  x3do:hasParent :Shape_2_3_1 ;
  x3do:hasFontStyle :FontStyle_2_3_1_1_1 ;
  x3do:string '"WaypointInterpolatorPrototype" "defines a prototype" "" "Click on this text to see" "WaypointInterpolatorExample" " scene"' .
:FontStyle_2_3_1_1_1 a owl:NamedIndividual, x3do:FontStyle ;
  x3do:hasParent :Text_2_3_1_1 ;
  x3do:justify '"MIDDLE" "MIDDLE"' .
:Appearance_2_3_1_2 a owl:NamedIndividual, x3do:Appearance ;
  x3do:hasParent :Shape_2_3_1 ;
  x3do:hasMaterial :Material_2_3_1_2_1 .
:Material_2_3_1_2_1 a owl:NamedIndividual, x3do:Material ;
  x3do:hasParent :Appearance_2_3_1_2 ;
  x3do:diffuseColor ( 1 1 0.2 ) .
:Shape_2_3_2 a owl:NamedIndividual, x3do:Shape ;
  x3do:hasParent :Anchor_2_3 ;
  x3do:hasGeometry :Box_2_3_2_1 ;
  x3do:hasAppearance :Appearance_2_3_2_2 .
:Box_2_3_2_1 a owl:NamedIndividual, x3do:Box ;
  x3do:hasParent :Shape_2_3_2 ;
  x3do:size ( 12 6.0 0.001 ) .
:Appearance_2_3_2_2 a owl:NamedIndividual, x3do:Appearance ;
  x3do:hasParent :Shape_2_3_2 ;
  x3do:hasMaterial :Material_2_3_2_2_1 .
:Material_2_3_2_2_1 a owl:NamedIndividual, x3do:Material ;
  x3do:hasParent :Appearance_2_3_2_2 ;
  x3do:diffuseColor ( 1 1 1 ) ;
  x3do:transparency 1 .
