<?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 content='MaterialChoicePrototype.x3d' name='title'/>
    <meta content='Prototype definition for toggle-able Material node that can choose among an array of material nodes, and is switchable at run time. This prototype also demonstrates why it is important that only the first node in a ProtoBody can render: so that it can serve as a special node type (such as Material).' name='description'/>
    <meta content='Don Brutzman and MV4205 class' name='creator'/>
    <meta content='29 April 2004' name='created'/>
    <meta content='28 November 2019' name='modified'/>
    <meta content='MaterialChoiceExample.x3d' name='reference'/>
    <meta content='https://www.web3d.org/x3d/content/examples/Savage/Tools/Animation/MaterialChoicePrototype.x3d' name='identifier'/>
    <meta content='X3D-Edit 3.2, https://www.web3d.org/x3d/tools/X3D-Edit' name='generator'/>
    <meta content='../../license.html' name='license'/>
  </head>
  <Scene>
    <WorldInfo title='MaterialChoicePrototype.x3d'/>
    <ProtoDeclare appinfo='MaterialChoice selects one of several different Material values' name='MaterialChoice'>
      <ProtoInterface>
        <field accessType='inputOnly' name='set_index' type='SFInt32'/>
        <field accessType='initializeOnly' appinfo='which Material node is chosen, with array index starting at 0' name='index' type='SFInt32' value='0'/>
        <field accessType='outputOnly' name='index_changed' type='SFInt32'/>
        <field accessType='inputOnly' name='set_fraction' type='SFFloat'/>
        <field accessType='initializeOnly' appinfo='fraction to interpolate between current and next Material node (if any) across all field values.' name='fraction' type='SFFloat' value='0'/>
        <field accessType='inputOnly' appinfo='Trigger next Material node' name='next' type='SFBool'/>
        <field accessType='inputOnly' appinfo='Trigger previous Material node' name='previous' type='SFBool'/>
        <field accessType='initializeOnly' appinfo='array of available Material nodes defaults to zeroth Material' name='materials' type='MFNode'>
          <!-- no default initialization node since it crashes Xj3D -->
        </field>
        <field accessType='inputOnly' appinfo='append another Material node to materials array' name='appendMaterial' type='SFNode'/>
        <field accessType='inputOnly' appinfo='delete Material node indicated by input index value' name='deleteMaterial' type='SFInt32'/>
        <field accessType='inputOnly' appinfo='deletes all Material nodes resets scene to default Material values defined in X3D Specification' name='deleteAllMaterials' type='SFBool'/>
      </ProtoInterface>
      <ProtoBody>
        <!-- first node is node type for this prototype, other nodes are active but do not render -->
        <Material DEF='ViewedMaterialNode'/>
        <Script DEF='MaterialAnimationScript' directOutput='true'>
          <field accessType='initializeOnly' name='traceEnabled' type='SFBool' value='false'/>
          <field accessType='inputOnly' name='set_index' type='SFInt32'/>
          <field accessType='initializeOnly' name='index' type='SFInt32'/>
          <field accessType='outputOnly' name='index_changed' type='SFInt32'/>
          <field accessType='inputOnly' name='set_fraction' type='SFFloat'/>
          <field accessType='initializeOnly' name='fraction' type='SFFloat'/>
          <field accessType='inputOnly' name='next' type='SFBool'/>
          <field accessType='inputOnly' name='previous' type='SFBool'/>
          <field accessType='initializeOnly' name='viewedMaterial' type='SFNode'>
            <Material USE='ViewedMaterialNode'/>
          </field>
          <field accessType='initializeOnly' name='defaultMaterial' type='SFNode'>
            <Material/>
          </field>
          <field accessType='initializeOnly' name='materials' type='MFNode'/>
          <field accessType='inputOnly' appinfo='append another Material node to materials array' name='appendMaterial' type='SFNode'/>
          <field accessType='inputOnly' appinfo='delete Material node indicated by input index value' name='deleteMaterial' type='SFInt32'/>
          <field accessType='inputOnly' appinfo='deletes all Material nodes resets scene to default Material values defined in X3D Specification' name='deleteAllMaterials' type='SFBool'/>
          <field accessType='initializeOnly' name='priorBadIndex' type='SFInt32' value='-1'/>
          <IS>
            <connect nodeField='set_index' protoField='set_index'/>
            <connect nodeField='index' protoField='index'/>
            <connect nodeField='index_changed' protoField='index_changed'/>
            <connect nodeField='set_fraction' protoField='set_fraction'/>
            <connect nodeField='fraction' protoField='fraction'/>
            <connect nodeField='next' protoField='next'/>
            <connect nodeField='previous' protoField='previous'/>
            <connect nodeField='materials' protoField='materials'/>
            <connect nodeField='appendMaterial' protoField='appendMaterial'/>
            <connect nodeField='deleteMaterial' protoField='deleteMaterial'/>
            <connect nodeField='deleteAllMaterials' protoField='deleteAllMaterials'/>
          </IS>
          <![CDATA[
ecmascript:

function initialize ()
{
   tracePrint ('initialize() begin...');
   if (materials[index] != null)
   {
	viewedMaterial.diffuseColor     = materials[index].diffuseColor;
	viewedMaterial.emissiveColor    = materials[index].emissiveColor;
	viewedMaterial.specularColor    = materials[index].specularColor;
	viewedMaterial.shininess        = materials[index].shininess;
	viewedMaterial.ambientIntensity = materials[index].ambientIntensity;
	viewedMaterial.transparency     = materials[index].transparency;
	interpolateMaterialValues();
   }
   else alwaysPrint ('warning: no initialization Material node provided for materials[' + index + ']'
                    + ', materials.length=' + materials.length);

   tracePrint ('initialize() complete');
}

function set_index (value, timestamp)
{
   if (value == index) return;
   else if ((value >= 0) && (value < materials.length))
   {
	index = value;
	viewedMaterial.diffuseColor     = materials[index].diffuseColor;
	viewedMaterial.emissiveColor    = materials[index].emissiveColor;
	viewedMaterial.specularColor    = materials[index].specularColor;
	viewedMaterial.shininess        = materials[index].shininess;
	viewedMaterial.ambientIntensity = materials[index].ambientIntensity;
	viewedMaterial.transparency     = materials[index].transparency;
	interpolateMaterialValues();
	index_changed = value; // chain input event to output event
	tracePrint ('set_index(' + value + ') complete');
   }
   else if (value == -1)
   {
	tracePrint ('set_index(' + value + ') out of range, use default Material values');
	index = value;
	viewedMaterial.diffuseColor     = defaultMaterial.diffuseColor;
	viewedMaterial.emissiveColor    = defaultMaterial.emissiveColor;
	viewedMaterial.specularColor    = defaultMaterial.specularColor;
	viewedMaterial.shininess        = defaultMaterial.shininess;
	viewedMaterial.ambientIntensity = defaultMaterial.ambientIntensity;
	viewedMaterial.transparency     = defaultMaterial.transparency;
	index_changed = value; // chain input event to output event
   }
   else if (priorBadIndex != value)
   {
	alwaysPrint ('set_index(' + value + ') out of range (only '
		+ materials.length + ' Material nodes)');
	priorBadIndex = value;
   }
}

function set_fraction(value, timestamp)
{
	tracePrint ('set_fraction(' + value + ')');
	if (fraction != value) // only update if value is changed
	{
		fraction = value;
		interpolateMaterialValues();
	}
}

function interpolateMaterialValues()
{
	f = fraction % 1;  // nonzero remainder (modulo 1) within range [0..1]
	tracePrint ('interpolateMaterialValues(index=' + index + ', fraction=' + fraction +  ', f=' + f + ')');
	// reset Material index if fraction is above 1
	if (Math.floor(fraction) >= 1)
	{
		index = Math.floor(fraction);
		tracePrint ('index=' + index + ' (reset from fraction)');
	}
	fraction = f;  // no need to retain integral part of fraction once index is set correspondingly
	// check OK to proceed
	if (index <  0) return;				// avoid index underflow
	if (index >= materials.length - 1) return;	// avoid index overflow
	if (f == 0.0) return; 				// avoid zero interpolation
	viewedMaterial.diffuseColor.r     = materials[index].diffuseColor.r +
		f * (materials[index+1].diffuseColor.r - materials[index].diffuseColor.r);
	viewedMaterial.diffuseColor.g     = materials[index].diffuseColor.g +
		f * (materials[index+1].diffuseColor.g - materials[index].diffuseColor.g);
	viewedMaterial.diffuseColor.b     = materials[index].diffuseColor.r +
		f * (materials[index+1].diffuseColor.b - materials[index].diffuseColor.b);
	tracePrint ('viewedMaterial.diffuseColor=' + viewedMaterial.diffuseColor);

	viewedMaterial.emissiveColor.r    = materials[index].emissiveColor.r +
		f * (materials[index+1].emissiveColor.r - materials[index].emissiveColor.r);
	viewedMaterial.emissiveColor.g    = materials[index].emissiveColor.r +
		f * (materials[index+1].emissiveColor.g - materials[index].emissiveColor.g);
	viewedMaterial.emissiveColor.b    = materials[index].emissiveColor.b +
		f * (materials[index+1].emissiveColor.b - materials[index].emissiveColor.b);

	viewedMaterial.specularColor.r    = materials[index].specularColor.r +
		f * (materials[index+1].specularColor.r - materials[index].specularColor.r);
	viewedMaterial.specularColor.g    = materials[index].specularColor.g +
		f * (materials[index+1].specularColor.g - materials[index].specularColor.g);
	viewedMaterial.specularColor.b    = materials[index].specularColor.b +
		f * (materials[index+1].specularColor.b - materials[index].specularColor.b);

	viewedMaterial.shininess        = materials[index].shininess +
		f * (materials[index+1].shininess - materials[index].shininess);
	viewedMaterial.ambientIntensity = materials[index].ambientIntensity +
		f * (materials[index+1].ambientIntensity - materials[index].ambientIntensity);
	viewedMaterial.transparency     = materials[index].transparency +
		f * (materials[index+1].transparency - materials[index].transparency);
}

function next(trigger)
{
	tracePrint ('next(' + trigger + ')');
	if ((trigger == true) && (materials.length > 0))
	{
		value = index + 1;
		if (value >= materials.length) value = 0;
		set_index (value); // update Material, test, etc.
	}
}

function previous(trigger)
{
	tracePrint ('previous(' + trigger + ')');
	if ((trigger == true) && (materials.length > 0))
	{
		value = index - 1;
		if (value < 0) value = materials.length - 1;
		set_index (value); // update Material, test, etc.
	}
}

function appendMaterial(newMaterial)
{
	tracePrint ('appendMaterial(' + newMaterial + ')');
	newMaterialCopy = new SFNode ('Material {}');
	tracePrint ('newMaterial.diffuseColor=' + newMaterial.diffuseColor);
	newMaterialCopy.diffuseColor     = newMaterial.diffuseColor;
	newMaterialCopy.emissiveColor    = newMaterial.emissiveColor;
	newMaterialCopy.specularColor    = newMaterial.specularColor;
	newMaterialCopy.shininess        = newMaterial.shininess;
	newMaterialCopy.ambientIntensity = newMaterial.ambientIntensity;
	newMaterialCopy.transparency     = newMaterial.transparency;
	materials[materials.length]      = newMaterialCopy;
	set_index (materials.length-1); // update Material, test, etc.
}

function deleteAllMaterials(trigger)
{
	if ((trigger == true) && (materials.length > 0))
	{
		materials.length = 0; // deletes all objects in materials MFNode array
		index = 0; // must be different to trigger node reset by set_index()
		set_index (-1);
		tracePrint ('deleteAllMaterials() materials.length=' + materials.length);
	}
}

function deleteMaterial(materialIndex)
{
	if ((materialIndex >= materials.length) || materialIndex < 0)
		alwaysPrint ('deleteMaterial(materialIndex=' + materialIndex + ') out of range, materials.length=' +  materials.length);
	else
	{
		for (i = materialIndex; i <= materials.length - 2; i++)
		{
			materials[i] = materials[i+1];
		}
		materials.length = materials.length - 1;
		if ((index == 0) && (materialIndex == 0) && (materials.length > 0))
		{
			index = -1; // allow reset back to 0
			set_index (0);
		}
		else if (index >= materialIndex) set_index (index-1); // decrement to remain same
		tracePrint ('deleteMaterial(materialIndex=' + materialIndex + ') complete,  materials.length=' + materials.length);
	}
}

function tracePrint(outputString)
{
	if (traceEnabled) Browser.println ('[MaterialChoice] ' + outputString);
}
function alwaysPrint(outputString)
{
	Browser.println ('[MaterialChoice] ' + outputString);
}
]]>
        </Script>
      </ProtoBody>
    </ProtoDeclare>
    <!-- ==================== -->
    <Anchor description='MaterialChoiceExample' url='"MaterialChoiceExample.x3d" "https://www.web3d.org/x3d/content/examples/Savage/Tools/Animation/MaterialChoiceExample.x3d" "MaterialChoiceExample.wrl" "https://www.web3d.org/x3d/content/examples/Savage/Tools/Animation/MaterialChoiceExample.wrl"'>
      <Shape>
        <Text string='"MaterialChoicePrototype" "defines a prototype" "" "Click text to see example scene" "MaterialChoiceExample"'>
          <FontStyle justify='"MIDDLE" "MIDDLE"' size='0.9'/>
        </Text>
        <Appearance>
          <Material diffuseColor='1 1 0.2'/>
        </Appearance>
      </Shape>
    </Anchor>
  </Scene>
</X3D>