#X3D V3.0 utf8 PROFILE Immersive # [X3D] version=3.0 # [X3D] noNamespaceSchemaLocation=https://www.web3d.org/specifications/x3d-3.0.xsd # X3D-to-ClassicVRML XSL translation autogenerated by X3dToClassicVrmlEncoding.xslt and X3dToVrml97.xslt # https://www.web3d.org/x3d/content/X3dToClassicVrmlEncoding.xslt # https://www.web3d.org/x3d/content/X3dToVrml97.xslt # Transformation using XSLT processor: Saxonica # head META "title" "AnimatedViewpointRecorderPrototype.x3d" META "description" "Record camera position and orientation as user navigates, then filter values and produce output, both into the console output window and as a replayable node group. Future work: further filtering." META "creator" "Don Brutzman, Ken Curtin, Duane Davis, Christos Kalogrias" META "created" "24 October 2003" META "modified" "12 October 2023" META "reference" "AnimatedViewpointRecorderExample.x3d" META "reference" "AnimatedViewpointRecorderSample.x3d" META "reference" "http://www.realism.com/Web3D/Examples#WhereAmI" META "reference" "http://www.realism.com/vrml/Example/WhereAmI/WhereAmI_Proto.wrl" META "subject" "recording animated viewpoint tour" META "identifier" "https://savage.nps.edu/Savage/Tools/Authoring/AnimatedViewpointRecorderPrototype.x3d" META "generator" "X3D-Edit 4.0, https://savage.nps.edu/X3D-Edit" META "license" "../../license.html" # [Scene] ========== ========== ========== # ==================== WorldInfo { title "AnimatedViewpointRecorderPrototype.x3d" } PROTO AnimatedViewpointRecorder [ # [appinfo] AnimatedViewpointRecorder captures view position and orientation tour to create a guided tour animation. The recording output goes to the browser console where the .x3d (or .x3dv) output can be cut/pasted for further use. inputOnly SFBool start # [appinfo] Set start=true to commence recording viewpoint position/orientation. inputOnly SFBool stop # [appinfo] Set stop=true to finish recording viewpoint position/orientation. Resulting VRML is added to scene resulting X3D and VRML is output to console. initializeOnly SFTime samplingInterval 0.1 # [appinfo] default 0.1 seconds initializeOnly SFBool outputX3D TRUE # [appinfo] whether to output .x3d syntax on browser console initializeOnly SFBool outputClassicVRML FALSE # [appinfo] whether to output .x3d syntax on browser console initializeOnly SFBool filterDeadTime FALSE # [appinfo] TODO not yet implemented ] { Group { children [ DEF NewViewpointGroup Group { } # it's a big old world out there! DEF WhereSensor ProximitySensor { size 1000000000 1000000000 1000000000 } DEF RecordingScript Script { inputOnly SFBool start IS start inputOnly SFBool stop IS stop initializeOnly SFTime samplingInterval IS samplingInterval # [appinfo] seconds initializeOnly SFBool outputX3D IS outputX3D # [appinfo] whether to output .x3d syntax on browser console initializeOnly SFBool outputClassicVRML IS outputClassicVRML # [appinfo] whether to output .x3d syntax on browser console outputOnly SFBool recordingInProgress # [appinfo] persistent state variable inputOnly SFVec3f set_position inputOnly SFRotation set_orientation initializeOnly MFVec3f positionArray [ ] initializeOnly MFTime positionTimeArray [ ] initializeOnly MFRotation orientationArray [ ] initializeOnly MFTime orientationTimeArray [ ] initializeOnly SFBool filterDeadTime IS filterDeadTime # [appinfo] not yet implemented initializeOnly SFNode newViewpointGroup USE NewViewpointGroup initializeOnly SFInt32 numberOfToursCreated 0 # [appinfo] persistent holding variable initializeOnly SFVec3f precedingPosition 0 0 0 # [appinfo] persistent holding variable initializeOnly SFRotation precedingOrientation 0 1 0 0 # [appinfo] persistent holding variable initializeOnly SFTime precedingPositionSampleTime 0 # [appinfo] persistent holding variable initializeOnly SFTime precedingOrientationSampleTime 0 # [appinfo] persistent holding variable initializeOnly SFFloat r 1 # [appinfo] normalization factor initializeOnly SFBool positionEventsReceived FALSE # [appinfo] track output of ProximitySensor initializeOnly SFBool orientationEventsReceived FALSE # [appinfo] track output of ProximitySensor directOutput TRUE ### Warning: Script initializeOnly field 'filterDeadTime' is not referenced in contained ecmascript: code ### Warning: Script 'var' declarations of variables are not persistent in contained ecmascript: code, values are lost after each call. Use definitions instead. url [ "ecmascript: // ### X3D Browser.print() not supported by all VRML97 viewers, instead simply using print() function initialize() { positionArray = new MFVec3f (); orientationArray = new MFRotation (); positionTimeArray = new MFTime (); orientationTimeArray = new MFTime (); positionEventsReceived = false; orientationEventsReceived = false; } function roundoff (value, digits) { resolution = 1; for (i = 1; i <= digits; i++ ) { resolution *= 10; } return Math.round (value*resolution) / resolution; // round to resolution } function filterPositions() { // TODO } function filterOrientations() { // TODO } function set_position (eventValue, timestamp) { // Browser.println ('position=' + eventValue); // we are counting on an initialization eventValue being sent by ProximitySensor positionEventsReceived = true; if ( positionArray.length == 0 ) { positionArray[0] = eventValue; // initialize positionTimeArray[0] = timestamp; // initialize } precedingPositionSampleTime = positionTimeArray[ positionArray.length - 1 ]; // seconds duration since last valid sample if ( (timestamp - precedingPositionSampleTime) > samplingInterval ) { // append values to each array positionArray[positionArray.length] = eventValue; positionTimeArray[positionTimeArray.length] = timestamp; } precedingPosition = eventValue; } function set_orientation (eventValue, timestamp) { // we are counting on an initialization eventValue being sent by ProximitySensor orientationEventsReceived = true; if ( orientationArray.length == 0 ) { r = Math.sqrt (eventValue.x*eventValue.x + eventValue.y*eventValue.y + eventValue.z*eventValue.z); // Browser.println ('orientation=' + eventValue.toString() + ', r=' + r); // trace if (r != 0) { eventValue.x = eventValue.x / r; eventValue.y = eventValue.y / r; eventValue.z = eventValue.z / r; } orientationArray[0] = eventValue; // initialize orientationTimeArray[0] = timestamp; // initialize } precedingOrientationSampleTime = orientationTimeArray[ orientationTimeArray.length - 1 ]; // append sample values to each array if ( (timestamp - precedingOrientationSampleTime) > samplingInterval ) { orientationTimeArray[orientationTimeArray.length] = timestamp; // normalize SFRotation axis if needed r = Math.sqrt (eventValue.x*eventValue.x + eventValue.y*eventValue.y + eventValue.z*eventValue.z); // Browser.println ('orientation=' + eventValue.toString() + ', r=' + r); // trace if (r != 0) { eventValue.x = eventValue.x / r; eventValue.y = eventValue.y / r; eventValue.z = eventValue.z / r; // auto append to array, no need to allocate orientationArray[orientationArray.length] = eventValue; } else // illegal zero-magnitude axis returned by browser, so just use previous rotation { // auto append to array, no need to allocate orientationArray[orientationArray.length] = precedingOrientation; } } precedingOrientation = eventValue; } function start (eventValue, timestamp) { if (eventValue == false) return; // only accept start if eventValue == true if (recordingInProgress == true) return; // ignore repeated starts while already running recordingInProgress = true; // arrays need to be reinitialized from previous run initialize(); Browser.println (' '); } function stop (eventValue, timestamp) { if (eventValue == false) return; // only accept stop if eventValue == true if (recordingInProgress == false) { Browser.println (' '); return; } // ensure legal array lengths in case some events were never sent due to not moving if (positionEventsReceived == false) { Browser.println (''); return; } if (orientationEventsReceived == false) { Browser.println (''); return; } recordingInProgress = false; // preceding last values were at last sampleInterval (either set_position or set_orientation) // add one more to each array since they are not sent values by sensor when not changing positionArray[ positionArray.length] = precedingPosition; orientationArray[orientationArray.length] = precedingOrientation; positionTimeArray[ positionTimeArray.length] = timestamp; orientationTimeArray[orientationTimeArray.length] = timestamp; if (positionArray.length != positionTimeArray.length) { Browser.println (''); } if (orientationArray.length != orientationTimeArray.length) { Browser.println (''); } filterPositions(); filterOrientations(); // iff events are sent simultaneously, could use either array with start/stop times synchronized // however that might be a bad assumption... so reset start times to match if (positionTimeArray[0] > orientationTimeArray[0]) positionTimeArray[0] = orientationTimeArray[0]; if (positionTimeArray[0] < orientationTimeArray[0]) orientationTimeArray[0] = positionTimeArray[0]; startTime = positionTimeArray[0]; stopTime = positionTimeArray[positionTimeArray.length-1]; interval = stopTime - startTime; x3dString = ' ' + ' \n' + ' \n' + ' ' + ' \n' + ' \n' + ' \n' + ' \n' + ' \n' + ' \n' + ' \n' + ' \n' + ' \n' + ' \n' + ' \n'; if (outputX3D) Browser.println (x3dString); vrmlString = '# ********** start recorded Animated Tour ' + numberOfToursCreated + ' using .x3dv syntax ********** \n' + 'Group {\n' + ' children [\n' + ' DEF AnimatedViewpointRecorderViewpoint' + numberOfToursCreated + ' Viewpoint {\n' + ' description \"Animated Tour ' + numberOfToursCreated + '\"\n' + ' orientation ' + orientationArray[0].x + ' ' + orientationArray[0].y + ' ' + orientationArray[0].z + ' ' + orientationArray[0].angle + '\n' + ' position ' + positionArray[0].x + ' ' + positionArray[0].y + ' ' + positionArray[0].z + '\n' + ' }\n' + ' DEF AnimatedViewpointRecorderTimer' + numberOfToursCreated + ' TimeSensor {\n' + ' cycleInterval ' + interval + '\n' + ' loop TRUE\n' + ' }\n' + ' DEF AnimatedViewpointRecorderPosition' + numberOfToursCreated + ' PositionInterpolator {\n' + ' key [\n'; for (counter = 0; counter < positionTimeArray.length; counter++) { vrmlString = vrmlString + roundoff(((positionTimeArray[counter] - positionTimeArray[0]) / interval),5) + ' \n'; } vrmlString = vrmlString + ' ]\n' + ' keyValue [\n'; for (counter = 0; counter < positionArray.length; counter++) { vrmlString = vrmlString + positionArray[counter].x + ' ' + positionArray[counter].y + ' ' + positionArray[counter].z + ', \n'; } vrmlString = vrmlString + ' ]\n' + ' }\n' + ' DEF AnimatedViewpointRecorderOrientation' + numberOfToursCreated + ' OrientationInterpolator {\n' + ' key [\n'; for (counter = 0; counter < orientationTimeArray.length; counter++) { vrmlString = vrmlString + roundoff(((orientationTimeArray[counter] - orientationTimeArray[0]) / interval),5) + ' \n'; } vrmlString = vrmlString + ' ]\n' + ' keyValue [\n'; for (counter = 0; counter < orientationArray.length; counter++) { vrmlString = vrmlString + orientationArray[counter].x + ' ' + orientationArray[counter].y + ' ' + orientationArray[counter].z + ' ' + orientationArray[counter].angle + ', \n'; } vrmlString = vrmlString + ' ]\n' + ' }\n' + ' Group {\n' + ' ROUTE AnimatedViewpointRecorderViewpoint' + numberOfToursCreated + '.bindTime TO AnimatedViewpointRecorderTimer' + numberOfToursCreated + '.startTime\n' + ' ROUTE AnimatedViewpointRecorderTimer' + numberOfToursCreated + '.fraction_changed TO AnimatedViewpointRecorderPosition' + numberOfToursCreated + '.set_fraction\n' + ' ROUTE AnimatedViewpointRecorderTimer' + numberOfToursCreated + '.fraction_changed TO AnimatedViewpointRecorderOrientation' + numberOfToursCreated + '.set_fraction\n' + ' ROUTE AnimatedViewpointRecorderPosition' + numberOfToursCreated + '.value_changed TO AnimatedViewpointRecorderViewpoint' + numberOfToursCreated + '.position\n' + ' ROUTE AnimatedViewpointRecorderOrientation' + numberOfToursCreated + '.value_changed TO AnimatedViewpointRecorderViewpoint' + numberOfToursCreated + '.orientation\n' + ' }\n' + ' ]\n' + '}\n'; Browser.println (); if (outputClassicVRML) Browser.println (vrmlString); numberOfToursCreated++; // TODO // newNode = new SFNode(vrmlString); // newViewpointGroup.children[numberOfToursCreated] = newNode; } " ] } DEF RouteHolder Group { ROUTE WhereSensor.position_changed TO RecordingScript.set_position ROUTE WhereSensor.orientation_changed TO RecordingScript.set_orientation ROUTE RecordingScript.recordingInProgress TO WhereSensor.enabled } ] } } # ==================== Background { groundColor [ 0.2 0.4 0.2 ] skyColor [ 0.2 0.2 0.4 ] } Viewpoint { description "Animated Viewpoint Recorder" position 0 0 14 } Anchor { description "AnimatedViewpointRecorder Example" url [ "AnimatedViewpointRecorderExample.x3d" "https://savage.nps.edu/Savage/Tools/Authoring/AnimatedViewpointRecorderExample.x3d" "AnimatedViewpointRecorderExample.wrl" "https://savage.nps.edu/Savage/Tools/Authoring/AnimatedViewpointRecorderExample.wrl" ] children [ Shape { geometry Text { string [ "AnimatedViewpointRecorderPrototype" "is a prototype definition file" "" "Click this text to see" "AnimatedViewpointRecorderExample" ] fontStyle FontStyle { justify [ "MIDDLE" "MIDDLE" ] size 1.2 } } appearance Appearance { material Material { diffuseColor 0.6 0.8 0.4 } } } ] }