<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE X3D PUBLIC "ISO//Web3D//DTD X3D 3.0//EN" "https://www.web3d.org/specifications/x3d-3.0.dtd">
<X3D profile='Immersive'  version='3.0 xmlns:xsd='http://www.w3.org/2001/XMLSchema-instance' xsd:noNamespaceSchemaLocation =' https://www.web3d.org/specifications/x3d-3.0.xsd ' >
<head>
<meta name='titlecontent=' IntegerSequencerPrototype.x3d '/>
<meta name='descriptioncontent='This proto, modeled after a ScalarInterpolator, generates an array of integer values based on the input fraction and keys.'/>
<meta name=' warning content=' MFInt32 keyValue accessType is listed as initializeOnly/field, since inputOutput cannot be translated to exposedField in VRML97 scripting. '/>
<meta name='creatorcontent='Don Brutzman, Estuko Lippi, Jeff Weekley, Jane Wu, Matthew Braun'/>
<meta name='createdcontent='20 August 2001'/>
<meta name='modifiedcontent='21 January 2020'/>
<meta name='referencecontent=' https://www.web3d.org/technicalinfo/specifications/vrml97/part1/nodesRef.html#ScalarInterpolator '/>
<meta name='subjectcontent='integer sequencer'/>
<meta name='identifiercontent=' https://www.web3d.org/x3d/content/examples/Basic/development/IntegerSequencerPrototype.x3d '/>
<meta name='generatorcontent='X3D-Edit 3.3, https://savage.nps.edu/X3D-Edit'/>
<meta name='licensecontent=' ../license.html'/>
</head>
<!--

to top <!-- Event Graph ROUTE Table shows event connections -->
 
<!-- Index for DEF nodes: KeyHolder, SequencerScript

Index for ProtoDeclare definition: IntegerSequencer
-->
<Scene>
<WorldInfo title='IntegerSequencerPrototype.x3d'/>
<ProtoDeclare name='IntegerSequencer'>
<ProtoInterface>
<field name='set_fractiontype='SFFloataccessType='inputOnly'
 appinfo='Regular interpolator-style input, range [0..1]' />

<field name='set_keytype='MFFloataccessType='inputOnly'
 appinfo='Array sequentially increasing typically [0..1]. Must have the same number of keys as keyValues.' />

<field name='keytype='MFFloataccessType='inputOutput'
 appinfo='Array sequentially increasing typically [0..1]. Must have the same number of keys as keyValues.' />

<field name='key_changedtype='MFFloataccessType='outputOnly'
 appinfo='Array sequentially increasing typically [0..1]. Must have the same number of keys as keyValues.' />

<field name='set_keyValuetype='MFInt32accessType='inputOnly'
 appinfo='Array of integer values. Must have the same number of keys as keyValues.' />

<field name='keyValuetype='MFInt32accessType='initializeOnly'
 appinfo='Array of integer values. Must have the same number of keys as keyValues.' />

<field name='keyValue_changedtype='MFInt32accessType='outputOnly'
 appinfo='Array of integer values. Must have the same number of keys as keyValues.' />

<field name='value_changedtype='SFInt32accessType='outputOnly'
 appinfo='Regular interpolator-style input' />

<field name='previoustype='SFBoolaccessType='inputOnly'
 appinfo='Utility method' />

<field name='nexttype='SFBoolaccessType='inputOnly'
 appinfo='Utility method' />
</ProtoInterface>
<ProtoBody>
<Group>
<Switch whichChoice='-1'>
<ScalarInterpolator DEF='KeyHolder'>
<IS>
<connect nodeField='keyprotoField='key'/>
</IS>
</ScalarInterpolator>
</Switch>
<Script DEF='SequencerScriptdirectOutput='true'>
<!-- Regular interpolator-style input -->
<field name='set_fractiontype='SFFloataccessType='inputOnly'
 appinfo='range [0..1]' />

<field name='set_keytype='MFFloataccessType='inputOnly'
 appinfo='Array sequentially increasing [0..1]. Must have the same number of keys as keyValues.' />

<field name='keyHolderNodetype='SFNodeaccessType='initializeOnly'>
<ScalarInterpolator USE=' KeyHolder'/>
</field>
<field name='key_changedtype='MFFloataccessType='outputOnly'
 appinfo='Array sequentially increasing [0..1]. Must have the same number of keys as keyValues.' />

<field name='set_keyValuetype='MFInt32accessType='inputOnly'
 appinfo='Array of integer values. Must have the same number of keys as keyValues.' />

<field name='keyValuetype='MFInt32accessType='initializeOnly'/>
<field name='keyValue_changedtype='MFInt32accessType='outputOnly'
 appinfo='Array of integer values. Must have the same number of keys as keyValues.' />

<!-- Regular interpolator-style output -->
<field name='value_changedtype='SFInt32accessType='outputOnly'/>
<!-- Utility methods -->
<field name='previoustype='SFBoolaccessType='inputOnly'/>
<field name='nexttype='SFBoolaccessType='inputOnly'/>
<field name='traceEnabledtype='SFBoolvalue='trueaccessType='initializeOnly'
 appinfo='For development use only not for inclusion in specification implementations.' />

<!-- Script-specific interfaces, not needed for node definition -->
<field name='previousFractiontype='SFFloatvalue='0.0accessType='initializeOnly'/>
<field name='nextIndextype='SFInt32value='0accessType='initializeOnly'/>
<field name='isValidtype='SFBoolvalue='trueaccessType='initializeOnly'/>
<field name='recheckValiditytype='SFBoolvalue='falseaccessType='initializeOnly'/>
<IS>
<connect nodeField='set_fractionprotoField='set_fraction'/>
<connect nodeField='set_keyprotoField='set_key'/>
<connect nodeField='key_changedprotoField='key_changed'/>
<connect nodeField='set_keyValueprotoField='set_keyValue'/>
<connect nodeField='keyValueprotoField='keyValue'/>
<connect nodeField='keyValue_changedprotoField='keyValue_changed'/>
<connect nodeField='value_changedprotoField='value_changed'/>
<connect nodeField='previousprotoField='previous'/>
<connect nodeField='nextprotoField='next'/>
</IS>
<![CDATA[
            
ecmascript:

var leftToRight;

function initialize()
{
	key      = keyHolderNode.key;
	tracePrint('Initializing a new IntegerSequencer.  key.length=' + key.length + '; keyValue.length=' + keyValue.length);
	tracePrint('key =' + key);
	tracePrint('keyValue =' + keyValue);

	validityCheck();
	setHalfKeyRange();

	// assume we start at first key, going left to right
	leftToRight = true;
	previousFraction = key[0];
	nextIndex = 1;  //validityCheck ensures minimum of 2 keys exist
}

function set_fraction(newFraction, timeStamp)
{
	if (recheckValidity) validityCheck();

	if (!isValid) return; //IntegerSequencer ignored

	//Bounds checking
	if (newFraction < key[0])
	{
		tracePrint('*** warning: fraction is less than first key.  fraction set to first key ***');
		newFraction = key[0];
	}
	else if (newFraction > key[key.length-1])
	{
		tracePrint('*** warning: fraction is greater than last key.  fraction set to last key ***');
		newFraction = key[key.length -1];
	}

	//Check animation direction
	if (newFraction < previousFraction && leftToRight == true)
	{
		if ((previousFraction - newFraction) > halfKeyRange) //looped around
		{
			nextIndex = 1;
		}
		else //just changed direction
		{
			leftToRight = false;
			nextIndex = nextIndex - 1;
            }
	}
	else if (newFraction > previousFraction && leftToRight == false)
	{
		if ((newFraction - previousFraction) < halfKeyRange) //looped around
		{
			nextIndex = key.length - 2;
		}
		else //just changed direction
		{
			leftToRight = true;
			nextIndex = nextIndex + 1;
            }
	}
	else if (newFraction == previousFraction)
	{ //no change, so no processing required
		return;
	}
	previousFraction = newFraction;

	if (leftToRight) // moving left to right
	{
		while (newFraction > key[nextIndex]) nextIndex++;

		if (newFraction == key[nextIndex])
			value_changed = keyValue[nextIndex];
		else	value_changed = keyValue[nextIndex -1];

		tracePrint('forward animation, fraction =' + newFraction);
		tracePrint('value_changed eventOut is:' + value_changed);
	}
	else // moving right to left
	{
		while (newFraction < key[nextIndex]) nextIndex--;

		if (newFraction == key[nextIndex])
			value_changed = keyValue[nextIndex];
		else	value_changed = keyValue[nextIndex + 1];

		tracePrint('backward animation, fraction =' + newFraction);
		tracePrint('value_changed eventOut is:' + value_changed);
	}
}

function set_key(newKey, timeStamp)
{
	key = newKey;
	keyHolderNode.key = newKey;
	setHalfKeyWidth();
	recheckValidity = true;
}

function set_keyValue(newKeyValue, timeStamp)
{
	keyValue = newKeyValue;
	recheckValidity = true;
}

function setHalfKeyRange()
{
	halfKeyRange = (key[key.length - 1] - key[0])/2.0;
}

function previous (value, timeStamp)
{
  if (value==true) // trigger on true events only
  {
	leftToRight = true;
	nextIndex = nextIndex - 2; // reset to previous
	if (nextIndex < 0) nextIndex = nextIndex + key.length;
	value_changed = keyValue[nextIndex];
	previousFraction = key[nextIndex];
	nextIndex++; // setup for next time, leftToRight
	if (nextIndex > key.length - 1) nextIndex = 0;
  }
}
function next (value, timeStamp)
{
  if (value==true) // trigger on true events only
  {
	leftToRight = true;
	value_changed = keyValue[nextIndex];
	previousFraction = key[nextIndex];
	nextIndex++; // setup for next time,leftToRight
	if (nextIndex > key.length - 1) nextIndex = 0;
  }
}

function validityCheck()
{
	//Check if lengths of key & keyValue arrays match
	if (key.length != keyValue.length)
	{
		alwaysPrint('*** error: key and keyValue arrays must be of the same length.  IntegerSequencer ignored ***');
		isValid = false;
		return;
	}
	//check to ensure minimum of 2 keys have been specified
	if (key.length < 2)
	{
		alwaysPrint('*** error: must contain at least 2 keys.  IntegerSequencer ignored ***');
		isValid = false;
		return;
	}

	//Check if key array has values in an non-decreasing order
	for (i = 1; i < key.length; i++)
	{
		tracePrint('i=' + i);

		if (key[i] < key [i-1])
		{
			alwaysPrint('*** error: key array values must be listed in a non-decreasing order.  IntegerSequencer ignored ***');
			isValid = false;
			return;
		}
	}
	isValid = true
	recheckValidity = false;
	key_changed = key;
	keyValue_changed = keyValue;
	return;
}

function tracePrint(outputString)
{
	if (traceEnabled) Browser.println ('[IntegerSequencer]' + outputString);
}

function alwaysPrint(outputString)
{
	Browser.println ('[IntegerSequencer]' + outputString);
}

          
]]>
</Script>
</Group>
</ProtoBody>
</ProtoDeclare>
<!-- ===============Example============== -->
<Anchor description='IntegerSequencerExampleparameter='"target=_blank"'
  url=' IntegerSequencerExample.x3d"https://savage.nps.edu/Savage/Tools/Animation/IntegerSequencerExample.x3d" "IntegerSequencerExample.wrl" "https://savage.nps.edu/Savage/Tools/Animation/IntegerSequencerExample.wrl" ' >
<Shape>
<Text string='"IntegerSequencerPrototype" "defines a prototype" "" "Click text to see example scene" "IntegerSequencerExample"'>
<FontStyle justify='"MIDDLE" "MIDDLE"size='0.9'/>
</Text>
<Appearance>
<Material diffuseColor='1 1 0.2'/>
</Appearance>
</Shape>
</Anchor>
</Scene>
</X3D>
<!--

to top <!-- Event Graph ROUTE Table shows event connections -->
 
<!-- Index for DEF nodes: KeyHolder, SequencerScript

Index for ProtoDeclare definition: IntegerSequencer
-->

Event Graph ROUTE Table with 0 ROUTE 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.

SequencerScript
Script
No ROUTE connection found for output from this node.
Contains SFNode field with indirect access to another node. 


-->

<!-- Online at
https://www.web3d.org/x3d/content/examples/Basic/development/IntegerSequencerPrototypeIndex.html -->
<!-- Version control at
https://sourceforge.net/p/x3d/code/HEAD/tree/www.web3d.org/x3d/content/examples/Basic/development/IntegerSequencerPrototype.x3d -->

<!-- Color 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> -->

to top <!-- For additional help information about X3D scenes, please see X3D Tooltips, X3D Resources, and X3D Scene Authoring Hints. -->