<?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='BathymetryGeneratorViaExtrusionPrototype.x3d' name='title'/>
    <meta content='This prototype generates bathymetry based on the input data, and uses Extrusion as the output geometry (with some problems as a result).' name='description'/>
    <meta content='Jane Wu' name='creator'/>
    <meta content='8 January 2002' name='created'/>
    <meta content='28 November 2019' name='modified'/>
    <meta content='bathymetry' name='subject'/>
    <meta content='https://www.web3d.org/technicalinfo/specifications/vrml97/part1/nodesRef.html#Extrusion' name='reference'/>
    <meta content='https://www.web3d.org/x3d/content/examples/Savage/Tools/Animation/BathymetryGeneratorViaExtrusionPrototype.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='BathymetryGeneratorViaExtrusionPrototype.x3d'/>
    <ProtoDeclare name='BathymetryGenerator'>
      <ProtoInterface>
        <field accessType='initializeOnly' name='positionArray' type='MFVec3f' value='0 0 0 10 -4 0 25 -6 0 30 -8 5 38 -15 5 45 -18 5 55 -22 5 60 -25 15 60 -27 22 55 -30 35 48 -35 35 35 -35 35 25 -45 35 20 -55 35 15 -70 35 3 -70 35 -5 -72 40 -5 -75 50 0 -80 55 15 -75 55 30 -70 55 35 -60 55 40 -50 55 50 -34 55 65 -23 70'/>
        <field accessType='initializeOnly' appinfo='for future development' name='timeArray' type='MFTime' value='1 3 6 8 10 12 14 15 17 18 23 28 35 37 39 43 45 47 48 53 58 60 61 65 70'/>
        <field accessType='initializeOnly' name='colorSchemeDepthRangeArray' type='MFVec2f' value='0 -10 -10 -20 -20 -30 -30 -40 -40 -50 -50 -60 -60 -70 -70 -999999'/>
        <field accessType='initializeOnly' name='colorSchemeColorArray' type='MFColor' value='1 1 0.2 0.6 1 1 0 1 1 0.2 0.6 0.2 1 0 1 0.56 0 0.32 0.2 0.3 0.7 0 0 1'/>
        <field accessType='initializeOnly' name='beamWidth' type='SFFloat' value='2'/>
        <field accessType='initializeOnly' name='surfaceTransparency' type='SFFloat' value='0.25'/>
        <field accessType='initializeOnly' name='traceEnabled' type='SFBool' value='false'/>
      </ProtoInterface>
      <ProtoBody>
        <Group>
          <Transform DEF='Bathymetry'/>
          <Script DEF='BathymetryScript' directOutput='true'>
            <field accessType='initializeOnly' name='positionArray' type='MFVec3f'/>
            <field accessType='initializeOnly' name='timeArray' type='MFTime'/>
            <field accessType='initializeOnly' name='colorSchemeDepthRangeArray' type='MFVec2f'/>
            <field accessType='initializeOnly' name='colorSchemeColorArray' type='MFColor'/>
            <field accessType='initializeOnly' name='beamWidth' type='SFFloat'/>
            <field accessType='initializeOnly' name='transparency' type='SFFloat'/>
            <field accessType='initializeOnly' name='spine' type='MFVec3f' value='0 0 0 0 1 0'/>
            <field accessType='initializeOnly' name='scale' type='MFVec2f' value='1 1'/>
            <field accessType='initializeOnly' name='orientation' type='MFRotation' value='0 0 1 0'/>
            <field accessType='initializeOnly' name='bathyColor' type='SFColor' value='1 1 1'/>
            <field accessType='outputOnly' name='bathyNodes' type='MFNode'/>
            <field accessType='initializeOnly' name='traceEnabled' type='SFBool'/>
            <field accessType='initializeOnly' name='coordinate' type='SFVec3f' value='0 0 0'/>
            <field accessType='initializeOnly' name='previousPosition' type='SFVec3f' value='0 0 0'/>
            <field accessType='initializeOnly' name='position' type='SFVec3f' value='0 0 0'/>
            <field accessType='initializeOnly' name='bathyNodeIndex' type='SFInt32' value='0'/>
            <IS>
              <connect nodeField='positionArray' protoField='positionArray'/>
              <connect nodeField='timeArray' protoField='timeArray'/>
              <connect nodeField='colorSchemeDepthRangeArray' protoField='colorSchemeDepthRangeArray'/>
              <connect nodeField='colorSchemeColorArray' protoField='colorSchemeColorArray'/>
              <connect nodeField='beamWidth' protoField='beamWidth'/>
              <connect nodeField='transparency' protoField='surfaceTransparency'/>
              <connect nodeField='traceEnabled' protoField='traceEnabled'/>
            </IS>
            <![CDATA[
ecmascript:

function initialize()
{
	bathyNodeIndex = 0;

	spineIndex = 0;
	position = positionArray[0];
	spine[spineIndex] = new SFVec3f(position.x, 0, position.z);
	scale[spineIndex] = new SFVec2f(1, Math.abs(position.y));
	spineIndex++;

	previousPosition = new SFVec3f(position.x, position.y, position.z);
	//Determine the initial depth range
	for (j = 0; j < colorSchemeDepthRangeArray.length; j++)
	{
		if (position.y >= colorSchemeDepthRangeArray[j].y)
			break;
	}
	currentDepthRangeIndex = j;

	for (i = 1; i < positionArray.length; i++)
	{			
		if (previousPosition.y == colorSchemeDepthRangeArray[currentDepthRangeIndex].y &&
		    positionArray[i].y != colorSchemeDepthRangeArray[currentDepthRangeIndex].y)
			terminateExtrusionSegmentWithCurrentPosition(currentDepthRangeIndex);

		//Update new position
		position = positionArray[i];

		//Determine the correct depth range
		if (position.y <= previousPosition.y)
		{
			for (j = currentDepthRangeIndex; j < colorSchemeDepthRangeArray.length; j++)
			{
				if (position.y >= colorSchemeDepthRangeArray[j].y)
					break;

				if (previousPosition.y != colorSchemeDepthRangeArray[currentDepthRangeIndex].y)
					terminateExtrusionSegmentWithDepthRangeBoundary(currentDepthRangeIndex);
			}
			currentDepthRangeIndex = j;
		}
		else
		{
			for (j = currentDepthRangeIndex; j > -1; j--)
			{
				if (position.y < colorSchemeDepthRangeArray[j-1].y)
					break;

				if (position.y > colorSchemeDepthRangeArray[j-1].y)
					terminateExtrusionSegmentWithDepthRangeBoundary(j-1);
			}
			currentDepthRangeIndex = j;
		}

		spine[spineIndex] = new SFVec3f(position.x, 0, position.z);
		scale[spineIndex] = new SFVec2f(1, Math.abs(position.y));
		spineIndex++;

		previousPosition = new SFVec3f(position.x, position.y, position.z);
	}
	terminateExtrusionSegmentWithCurrentPosition(currentDepthRangeIndex);
}

function terminateExtrusionSegmentWithDepthRangeBoundary(index)
{
	depthRange = colorSchemeDepthRangeArray[index];

	findCoordinate(previousPosition.x, position.x, previousPosition.y, position.y, depthRange.y);
	xPrime = coordinate;
	findCoordinate(previousPosition.z, position.z, previousPosition.y, position.y, depthRange.y);
	zPrime = coordinate;
	spine[spineIndex] = new SFVec3f(xPrime, 0, zPrime);
	scale[spineIndex] = new SFVec2f(1, Math.abs(depthRange.y));

	if (scale[scale.length-2].y > scale[scale.length-1].y)
		color = colorSchemeColorArray[index+1];
	else
		color = colorSchemeColorArray[index];

	createExtrusionShape(spine, scale, color);

	//Reset values to start the next extrustion segment
	spineIndex = 0;
	resetSpine();
	resetScale();

	//Update the current segment end as the start of the next segment
	spine[spineIndex] = new SFVec3f(xPrime, 0, zPrime);
	scale[spineIndex] = new SFVec2f(1, Math.abs(depthRange.y));
	spineIndex++;	
}

function terminateExtrusionSegmentWithCurrentPosition(index)
{
	if (scale[scale.length-1].y != Math.abs(colorSchemeDepthRangeArray[index].y))
		index--;

	if (scale[scale.length-2].y > scale[scale.length-1].y)
		color = colorSchemeColorArray[index+1];
	else
		color = colorSchemeColorArray[index];

	createExtrusionShape(spine, scale, color);

	//Reset values to start the next extrustion segment
	spineIndex = 0;
	resetSpine();
	resetScale();

	//Update the current segment end as the start of the next segment
	spine[spineIndex] = new SFVec3f(position.x, 0, position.z);
	scale[spineIndex] = new SFVec2f(1, Math.abs(position.y));
	spineIndex++;

	//Update the previousPosition
	previousPosition = new SFVec3f(position.x, position.y, position.z);
}

function findCoordinate(x1, x2, y1, y2, yPrime)
{
	coordinate = ((x1 - x2) / (y1 - y2)) * yPrime + ((x2*y1 - x1*y2) / (y1 - y2));
}

function createExtrusionShape(spine, scale, color)
{
	determineOrientation(spine);
	tracePrint('An extrusion is created whose spine is: ' + spine);
	tracePrint('and scale is: ' + scale);
	tracePrint('orientation is: ' + orientation);
	tracePrint('color is: ' + color);
	alwaysPrint('number of spine points is: ' + spine.length);
	alwaysPrint('orientation is: ' + orientation);

	//Build the VRML string
	extrusionSyntax  = 'Shape {\n';
	extrusionSyntax += '   appearance Appearance {' + '\n';
	extrusionSyntax += '      material Material {' + '\n';
	extrusionSyntax += '         diffuseColor ' + color + '\n';
	extrusionSyntax += '         transparency ' + transparency + '\n';
	extrusionSyntax += '      }' + '\n';
	extrusionSyntax += '   }' + '\n';
	extrusionSyntax += '   geometry Extrusion {' + '\n';
	extrusionSyntax += '      crossSection [' + (beamWidth/(-2)) + ', 1, ' + (beamWidth/2) + ', 1, ' + (beamWidth/(-2)) + ', 1]' + '\n';
	extrusionSyntax += '      scale ' + scale + '\n';
	extrusionSyntax += '      spine ' + spine + '\n';
	extrusionSyntax += '      orientation ' + orientation + '\n';
	extrusionSyntax += '      creaseAngle 1.57' + '\n';
	extrusionSyntax += '   }' + '\n';
	extrusionSyntax += '}';

	//Create Extrusion shape
	tracePrint (extrusionSyntax);
	bathySegment = new SFNode(extrusionSyntax);

	bathyNodes[bathyNodeIndex] = bathySegment;
	bathyNodeIndex++;
}

function determineOrientation(spine)
{
   previousZAxis = null;
   orientation = new MFRotation();
   //Special cases
   if (spine.length == 2)
   {
      if (spine[0].z == spine[1].z)
      {
         if (spine[0].x <= spine[1].x) //positive x direction
            orientation[0] = orientation[1] = new SFRotation(0, 1, 0, 1.57);
         else //negative x direction
            orientation[0] = orientation[1] = new SFRotation(0, 1, 0, -1.57);
      }
      else
      {
         if (spine[0].x == spine[1].x) //parallet to the z axis
            orientation[0] = orientation[1] = new SFRotation(0, 1, 0, 0);
         else
         {
            angleRadian = Math.atan((spine[0].x- spine[1].x) / (spine[0].z - spine[1].z));
//          angleRadian = Math.atan2((spine[0].x- spine[1].x), (spine[0].z - spine[1].z));
            
            orientation[0] = orientation[1] = new SFRotation(0, 1, 0, angleRadian);
         }
      }
      return;
   }

   for (n = 0; n < spine.length; n++)
   {
      //If spine is not closed, the Z axis used for the first spine point is the same as the Z axis for spine[1].
      //The Z axis used for the last spine point is the same as the Z axis for spine[spine.length - 2].   
      if (n == 0)
         si = 1;
      else if (n == (spine.length - 1))
         si = spine.length - 2;
      else
         si = n;

      zAxis = (spine[si+1].subtract(spine[si])).cross((spine[si-1].subtract(spine[si])));

      while (zAxis.x == 0 && zAxis.y == 0 && zAxis.z == 0)
      {
         if (previousZAxis == null)
         {
            ++si;
            if (si == (spine.length - 1)) //The entire spine is collinear
            {
               zAxis = new SFVec3f(1, 0, 0);
               break;
            }

            zAxis = (spine[si+1].subtract(spine[si])).cross((spine[si-1].subtract(spine[si])));
         }
         else
            zAxis = new SFVec3f(previousZAxis.x, previousZAxis.y, previousZAxis.z);
      }

      adjustedZAxis = zAxis;
      if (n == 0)
         previousZAxis = zAxis;
      else
      {
         dotProduct = zAxis.dot(previousZAxis);
         if (dotProduct < 0)
            adjustedZAxis = new SFVec3f(zAxis.multiply(-1).x, zAxis.multiply(-1).y, zAxis.multiply(-1).z);

         previousZAxis = adjustedZAxis;
      }
      
      zAxisNormalized = adjustedZAxis.normalize();
      theta = Math.acos(zAxisNormalized.dot(new SFVec3f(0, -1, 0)));
      if (spine[1].x < spine[0].x)
         orientation[n] = new SFRotation(0, -1, 0, theta);
      else
         orientation[n] = new SFRotation(0, 1, 0, theta);
   }
if (theta == 0)
   Browser.println ('rotation angle = ' + theta);
else if (theta > 1.57 && theta < 3.14)
   Browser.println ('rotation angle = ' + theta);
else if (theta > 3.14)
   Browser.println ('rotation angle = ' + theta);
}

function resetSpine()
{
	spine = new MFVec3f();
}

function resetScale()
{
	scale = new MFVec2f();
}

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

function alwaysPrint(string)
{
	Browser.println ('[BathymetryGenerator] ' + string);
}
]]>
          </Script>
          <ROUTE fromField='bathyNodes' fromNode='BathymetryScript' toField='addChildren' toNode='Bathymetry'/>
          <Shape>
            <Extrusion/>
          </Shape>
        </Group>
      </ProtoBody>
    </ProtoDeclare>
    <Viewpoint description='MainView' position='0 -50 200'/>
    <ProtoInstance name='BathymetryGenerator'>
      <fieldValue name='positionArray' value='0 0 0 10 -4 0 25 -6 0 30 -8 5 38 -15 5 45 -18 5 55 -22 5 60 -25 15 60 -27 22 55 -30 35 48 -35 35 35 -35 35 25 -45 35 20 -55 35 15 -70 35 3 -70 35 -5 -72 40 -5 -75 50 0 -80 55 15 -75 55 30 -70 55 35 -60 55 40 -50 55 50 -34 55 65 -23 70'/>
      <fieldValue name='surfaceTransparency' value='0.25'/>
      <fieldValue name='traceEnabled' value='true'/>
    </ProtoInstance>
  </Scene>
</X3D>