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.
-->