#X3D V3.0 utf8
PROFILE Interchange

# works at least with InstantPlayer, freewrl
#
# A VRML Proto to account the data for a PositionInterpolator and a 
# OrientationInterpolator from NurbsCurve data by scripting 
# (slow)
# Copyright (C) 2006, 2019 J. "MUFTI" Scheurich
#
# Developed from NodeNurbsSurface.cpp of the vrml97 editor dune 
# Copyright (C) 1999 Stephen F. White
#
# exampe use
#
# EXTERNPROTO CurveAnimation
#    [
#    input      SFFloat    set_fraction
#    outputOnly     SFVec3f    position_changed
#    outputOnly     SFRotation orientation_changed
#    inputoutputOnly MFVec3f    controlPoint        
#    inputoutputOnly SFInt32    tessellation        
#    inputoutputOnly MFFloat    weight              
#    field        MFFloat    knot                
#    field        SFInt32    order               
#    inputoutputOnly SFVec3f    rotationAxis
#    inputoutputOnly SFBool     enableRotation
#    inputoutputOnly SFBool     hover
#    ]
# [
# "CurveAnimationPROTO.wrl"
# ]
# 
# 
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program (see the file "COPYING" for details); if 
# not, write to the Free Software Foundation, Inc., 675 Mass Ave, 
# Cambridge, MA 02139, USA.
#
PROTO CurveAnimation
   [
   inputOnly      SFFloat    set_fraction
   outputOnly     SFVec3f    position_changed
   outputOnly     SFRotation orientation_changed
   inputOutput MFVec3f    controlPoint          []
   inputOutput SFInt32    tessellation          0      # (-&infin;,&infin;)
   inputOutput MFFloat    weight                []     # (0,&infin;)
   field        MFFloat    knot                 []     # (-&infin;,&infin;)
   field        SFInt32    order                3      # [2,&infin;) 
   inputOutput SFVec3f    rotationAxis          0 1 0
   inputOutput SFBool     enableRotation        TRUE  
   inputOutput SFBool     hover                 TRUE
   ]
{
Group
  {
  children
    [
    DEF POSITION_INTERPOLATOR PositionInterpolator 
      {
      set_fraction IS set_fraction
      value_changed IS position_changed
      }
    DEF ORIENTATION_INTERPOLATOR OrientationInterpolator 
      {
      set_fraction IS set_fraction
      value_changed IS orientation_changed
      }
    ]
  }

DEF NURBS_SCRIPT Script 
  {
  directOutput TRUE  

  inputOnly SFFloat set_fraction IS set_fraction
  outputOnly SFFloat set_fraction_out

  inputOutput MFVec3f controlPoint IS controlPoint
  inputOutput SFInt32 tessellation IS tessellation
  inputOutput MFFloat weight IS weight

  field MFFloat knot IS knot
  field SFInt32 order IS order

  inputOutput SFVec3f rotationAxis IS rotationAxis

  field SFBool enableRotation TRUE

  outputOnly MFFloat positionKey
  outputOnly MFVec3f positionKeyValue

  outputOnly MFFloat orientationKey
  outputOnly MFRotation orientationKeyValue

  field SFNode positionInterpolator USE POSITION_INTERPOLATOR 
  field SFNode orientationInterpolator USE ORIENTATION_INTERPOLATOR 

  field MFFloat weights []
  field MFVec3f tess []
  field MFFloat w []
  field MFVec2f tc []
  field MFInt32 ci []

  field MFFloat basis []
  field MFFloat deriv []

  field SFVec3f s 0 0 0
  field SFVec3f u 0 0 0

  field SFVec3f n 0 0 0

  field MFFloat left []
  field MFFloat right []

  field SFInt32 dimension 0
  url 
    [
    "javascript:

    function findSpan(dimension, order, u, knots) {
       var low;
       var mid;
       var high;
       var n;

       n = dimension + order - 1;

       if (u >= knots[n]) {
           return n - order;
       }
       low = order - 1;
       high = n - order + 1;

       mid = Math.floor((low + high) / 2);

       while ((u < knots[mid]) || (u >= knots[mid+1])) {
           if (u < knots[mid])
               high = mid;
           else
               low = mid;
           mid = Math.floor((low + high) / 2);
       }

       return Math.floor(mid);
    }

    function basisFuns(span, u, order, knots, basis, deriv) {
       var j;
       var saved;
       var dsaved;
       var r;
       var temp;

       basis[0] = 1.0;
       for (j = 1; j < order; j++) {
           left[j] = u - knots[span+1-j];
           right[j] = knots[span+j]-u;
           saved = 0.0;
           dsaved = 0.0;
           for (r = 0; r < j; r++) {
               temp = basis[r] / (right[r+1] + left[j-r]);
               basis[r] = saved + right[r+1] * temp;
               deriv[r] = dsaved - j * temp;
               saved = left[j-r] * temp;
               dsaved = j * temp;
           }
	   basis[j] = saved;
	   deriv[j] = dsaved;
       }
    }


    function linePoint(weight,u,ind) {
       var i;
       var j;

       var span;

       var base;

       var index;

       var w;
       var dw;

       var gain;
       var dgain;

       span = findSpan(dimension, order, u, knot);

       basisFuns(span, u, order, knot, basis, deriv);

       base = span-order+1;

       index = base;

       s=new SFVec3f(0.0, 0.0, 0.0);
       w = 0.0;
       dw = 0.0;
       for (i = 0; i < order; i++) {
           gain = basis[i];
           dgain = deriv[i];
           s.x += controlPoint[index].x * gain;
           s.y += controlPoint[index].y * gain;
           s.z += controlPoint[index].z * gain;
           w += weight[index] * gain;
           index++;
       }
       s.x = s.x / w;
       s.y = s.y / w;
       s.z = s.z / w;

       return s;
    }


    function newQuat(x, y, z, a) {
       var r;
       r = new MFFloat();
       r[0] = x;
       r[1] = y;
       r[2] = z;
       r[3] = a;
       return r;
    }

    function quatNorm(quat) {
       var rlen;
       rlen = Math.sqrt(quat[0] * quat[0] + quat[1] * quat[1] + 
                        quat[2] * quat[2] + quat[3] * quat[3]);
       if (rlen > 0.000001)
          rlen = 1.0 / rlen;
       else
          rlen = 1.0;
       quat[0] *= rlen;
       quat[1] *= rlen;
       quat[2] *= rlen;
       quat[3] *= rlen;
       return quat;
    }

    function quatMult(q1, q2) {
       var r;
       r = newQuat(0, 0, 1, 0);
       r[0] = q2[3] * q1[0] + q2[0] * q1[3] + q2[1] * q1[2] - q2[2] * q1[1];
       r[1] = q2[3] * q1[1] + q2[1] * q1[3] + q2[2] * q1[0] - q2[0] * q1[2];
       r[2] = q2[3] * q1[2] + q2[2] * q1[3] + q2[0] * q1[1] - q2[1] * q1[0];
       r[3] = q2[3] * q1[3] - q2[0] * q1[0] - q2[1] * q1[1] - q2[2] * q1[2];
       r = quatNorm(r);
       return r;
    }

    function accountAngle(vec1, vec2) {
       var vec1len;
       var vec2len;
       var sinangle;
       var cosangle;
       var angle;
      
       vec1len = vec1.length();
       vec2len = vec2.length();
       if ((vec1len == 0) || (vec2len == 0))
          return 0;
       sinangle = vec1.cross(vec2).length() / vec1len / vec2len;
       cosangle = vec1.dot(vec2) / vec1len / vec2len;
       if (sinangle < 1e-9)
          return 0;
       angle = Math.atan2(sinangle, cosangle);

       return angle;
    }


    function accountProjection(vector, normal) {
       var angle;
       var mult;

       angle = accountAngle(vector, normal);
       mult = vector.length() * Math.cos(angle);
       return new SFVec3f(vector.x - normal.x * mult,
                          vector.y - normal.y * mult,
                          vector.z - normal.z * mult);
    }

    function accountQuat(vec1, vec2) {
       var rotAxis;
       var axis;
       var axisLength;
       var rot;
       var angle;
       var sinAngle;

       rotAxis = vec1.cross(vec2);
       axisLength = rotAxis.length();
       axis = new MFFloat();
       if (axisLength < 0.0000001) {
          axis[0] = 0;
          axis[1] = 0;
          axis[2] = 0;
       } else {
          axis[0] = rotAxis.x / axisLength; 
          axis[1] = rotAxis.y / axisLength; 
          axis[2] = rotAxis.z / axisLength; 
       }
       angle = accountAngle(vec1, vec2);
       sinAngle = Math.sin(0.5 * angle);
       rot = newQuat(axis[0] * sinAngle, 
                     axis[1] * sinAngle, 
                     axis[2] * sinAngle, 
                     Math.cos(0.5 * angle));
       rot = quatNorm(rot);
       return rot;
    }

    function sendToOrientationInterpolator() {
       var i;
       var j;
       var chainLength;
       var chainRot;
       var oldQuat;
       var correctionX;
       var correctionY;
       var normal;
       var vector1;
       var point3;
       var point2;
       var vector2;
       var vec1;
       var vec2
       var quat;
       var rlen;
       var axis;
       var rot;
       var flipAxis;
       var flip;

       if (tess.length == 0) 
          makeChain();

       chainLength = tess.length;
       if (chainLength < 1)
          return;
       chainRot = new MFFloat(chainLength * 4);
       correctionX = newQuat(1, 0, 0, Math.cos(0.5 * Math.PI));
       correctionY = newQuat(0, 1, 0, Math.cos(0.5 * Math.PI));
       oldQuat = newQuat(0, 0, 1, 0);
       quat = newQuat(0, 0, 1, 0);
       for (j = 0; j < (chainLength - 1); j++) {
          normal = rotationAxis;
          vector1 = new SFVec3f(0, 0, 1);
          point3 = new SFVec3f (tess[j + 1].x,  tess[j + 1].y, tess[j + 1].z);
          point2 = new SFVec3f (tess[j    ].x,  tess[j    ].y, tess[j    ].z);
          if (j > 0) {
             point1 = new SFVec3f(tess[j - 1].x,  tess[j - 1].y, tess[j - 1].z);
             vector1 = new SFVec3f(point2.x - point1.x,
                                   point2.y - point1.y,
                                   point2.z - point1.z);
          }
          vector2 = new SFVec3f(point3.x - point2.x,
                                point3.y - point2.y,
                                point3.z - point2.z);
          vec1 = accountProjection(vector1, normal);
          vec2 = accountProjection(vector2, normal);
          quat = accountQuat(vec1, vec2);
          quat = quatMult(quat, oldQuat);
          if (j == 0) {
             quat = quatMult(quat, correctionY); 
             quat = quatMult(quat, correctionX); 
          }
          rlen = Math.sqrt(quat[0] * quat[0] + quat[1] * quat[1] + 
                           quat[2] * quat[2]);
          axis = new SFVec3f(quat[0], quat[1], quat[2]); 
          axis.normalize();
          rot = new SFRotation(axis, 2.0 * Math.acos(quat[3]));
          flipAxis = new SFVec3f(0, 1, 0);
          flip = new SFRotation(flipAxis, 3.141592);
          rot = rot.multiply(flip); 
          for (i = 0; i < 4; i++)
             chainRot[j * 4 + i] = rot[i];
          oldQuat = quat;
       }    

       if (chainLength > 0) {
          oldQuat[1] *= -1;
          for (i = 0; i < 4; i++)
             chainRot[(chainLength - 1) * 4 + i] =  oldQuat[i];
       }
       orientationInterpolator.key = new MFFloat();
       orientationInterpolator.keyValue = new MFRotation();
       for (i = 0; i < tess.length; i++) {
           if (tess.length == 1)
               orientationKey[i] = 0;
           else
               orientationKey[i] = (1.0 / (tess.length - 1)) * i;
           orientationKeyValue[i].x = chainRot[i * 4];
           orientationKeyValue[i].y = chainRot[i * 4 + 1];
           orientationKeyValue[i].z = chainRot[i * 4 + 2];
           orientationKeyValue[i].angle = chainRot[i * 4 + 3];

       }
    }

    function sendToPositionInterpolator() {
       positionKey = new MFFloat();
       positionKeyValue = new MFVec3f();
       for (i = 0; i < tess.length; i++) {
           if (tess.length == 1)
               positionKey[i] = 0;
           else
               positionKey[i] = (1.0 / (tess.length - 1)) * i;
           positionKeyValue[i].x = tess[i].x;
           positionKeyValue[i].y = tess[i].y;
           positionKeyValue[i].z = tess[i].z;
       }
    }
  
    function makeChain() {
       var size;
       var i;
       var j;
       var index;
       var u;
       var inv;
       var inc;
       var uTess;        

       index=0;

       weights = new MFFloat();

       dimension = controlPoint.length;

       if (dimension == 0) return;

       if (knot.length != order + dimension) {
           print('no NurbsCurve: knot.length!=order+dimension');
           return;
       }
   
       if (weight.length == 0) {
          weights = new MFFloat();
          for (i = 0; i < controlPoint.length; i++) {
               weights[i] = 1.0;
          }
       } else if (weight.length != controlPoint.length) {
           print('no NurbsCurve: weight.length!=controlPoint.length');
           return;
       }
   
       uTess=tessellation;

       if (uTess <= 0) uTess = 32;

       tess = new MFVec3f();

       size = (uTess + 1);
   
       if (knot.length != 0)
           inc = (knot[knot.length-1] - knot[0]) / uTess;
       else
           print('knot.length = 0 ');

       w = (weight.length == 0) ? weights : weight;
       u = knot[0];

       for (i = 0; i <= uTess; i++) {
           tess[i] = linePoint(w,u,i);
           index++;
           u += inc;
       }
    }

    function initialize() {
       makeChain();
       sendToPositionInterpolator();
       sendToOrientationInterpolator();       
    }

    function set_fraction(value, time) {
    }

    function controlPoint(value, time) {
       initialize();
    }

    function tessellation(value, time) {
       initialize();
    }

    function weight(value, time) {
       initialize();
    }

    function rotationAxis(value, time) {
       initialize();
    }
    "
    ]
  }
  ROUTE NURBS_SCRIPT.positionKey TO POSITION_INTERPOLATOR.key
  ROUTE NURBS_SCRIPT.positionKeyValue TO POSITION_INTERPOLATOR.keyValue

  ROUTE NURBS_SCRIPT.orientationKey TO ORIENTATION_INTERPOLATOR.key
  ROUTE NURBS_SCRIPT.orientationKeyValue TO ORIENTATION_INTERPOLATOR.keyValue
}

