/*
 * Copyright 1997-2008 Sun Microsystems, Inc.  All Rights Reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Sun designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Sun in the LICENSE file that accompanied this code.
 *
 * This code 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
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
 * CA 95054 USA or visit www.sun.com if you need additional information or
 * have any questions.
 *
 */

package javax.media.j3d;


/**
 * The alpha NodeComponent object provides common methods for
 * converting a time value into an alpha value (a value in the range 0
 * to 1).  The Alpha object is effectively a function of time that
 * generates alpha values in the range [0,1] when sampled: f(t) =
 * [0,1].  A primary use of the Alpha object is to provide alpha
 * values for Interpolator behaviors.  The function f(t) and the
 * characteristics of the Alpha object are determined by
 * user-definable parameters:
 *
 * <p>
 * <ul>
 *
 * <code>loopCount</code> -- This is the number of times to run this
 * Alpha; a value of -1 specifies that the Alpha loops
 * indefinitely.<p>
 *
 * <code>triggerTime</code> -- This is the time in milliseconds since
 * the start time that this object first triggers.  If (startTime +
 * triggerTime >= currentTime) then the Alpha object starts running.<p>
 *
 * <code>phaseDelayDuration</code> -- This is an additional number of
 * milliseconds to wait after triggerTime before actually starting
 * this Alpha.<p>
 *
 * <code>mode</code> -- This can be set to INCREASING_ENABLE,
 * DECREASING_ENABLE, or the Or'ed value of the two.
 * INCREASING_ENABLE activates the increasing Alpha parameters listed
 * below; DECREASING_ENABLE activates the decreasing Alpha parameters
 * listed below.<p>
 *
 * </ul> Increasing Alpha parameters:<p> <ul>
 *
 * <code>increasingAlphaDuration</code> -- This is the period of time
 * during which Alpha goes from zero to one. <p>
 *
 * <code>increasingAlphaRampDuration</code> -- This is the period of
 * time during which the Alpha step size increases at the beginning of
 * the increasingAlphaDuration and, correspondingly, decreases at the
 * end of the increasingAlphaDuration.  This parameter is clamped to
 * half of increasingAlphaDuration.  When this parameter is non-zero,
 * one gets constant acceleration while it is in effect; constant
 * positive acceleration at the beginning of the ramp and constant
 * negative acceleration at the end of the ramp.  If this parameter is
 * zero, then the effective velocity of the Alpha value is constant
 * and the acceleration is zero (ie, a linearly increasing alpha
 * ramp).<p>
 *
 * <code>alphaAtOneDuration</code> -- This is the period of time that
 * Alpha stays at one.<p> </ul> Decreasing Alpha parameters:<p> <ul>
 *
 * <code>decreasingAlphaDuration</code> -- This is the period of time
 * during which Alpha goes from one to zero.<p>
 *
 * <code>decreasingAlphaRampDuration</code> -- This is the period of
 * time during which the Alpha step size increases at the beginning of
 * the decreasingAlphaDuration and, correspondingly, decreases at the
 * end of the decreasingAlphaDuration.  This parameter is clamped to
 * half of decreasingAlphaDuration.  When this parameter is non-zero,
 * one gets constant acceleration while it is in effect; constant
 * positive acceleration at the beginning of the ramp and constant
 * negative acceleration at the end of the ramp.  If this parameter is
 * zero, the effective velocity of the Alpha value is constant and the
 * acceleration is zero (i.e., a linearly-decreasing alpha ramp).<p>
 *
 * <code>alphaAtZeroDuration</code> -- This is the period of time that
 * Alpha stays at zero.
 *
 * </ul>
 *
 * @see Interpolator
 */

public class Alpha extends NodeComponent {

    // loopCount <  -1 --> reserved
    // loopCount == -1 --> repeat forever
    // loopCount >=  0 --> repeat count
    private int loopCount;

    /**
     * Specifies that the increasing component of the alpha is used.
     */
    public static final int INCREASING_ENABLE = 1;

    /**
     * Specifies that the decreasing component of the alpha is used
     */
    public static final int DECREASING_ENABLE = 2;

    /**
     * This alpha's mode, specifies whether to process
     * increasing and decreasing alphas.
     */
    private int mode;

    private float triggerTime;
    private float phaseDelay;
    private float increasingAlpha;
    private long increasingAlphaRamp;
    private float incAlphaRampInternal;
    private float alphaAtOne;
    private float decreasingAlpha;
    private long decreasingAlphaRamp;
    private float decAlphaRampInternal;
    private float alphaAtZero;

    // For pausing and resuming Alpha
    private long pauseTime = 0L;
    private boolean paused = false;

    // Stop time gets used only for loopCount > 0
    private float stopTime;

    // Start time in milliseconds
    private long startTime = MasterControl.systemStartTime;

    /**
     * Constructs an Alpha object with default parameters.  The default
     * values are as follows:
     * <ul>
     * loopCount			: -1<br>
     * mode				: INCREASING_ENABLE<br>
     * startTime			: system start time<br>
     * triggerTime			: 0<br>
     * phaseDelayDuration		: 0<br>
     * increasingAlphaDuration		: 1000<br>
     * increasingAlphaRampDuration	: 0<br>
     * alphaAtOneDuration		: 0<br>
     * decreasingAlphaDuration		: 0<br>
     * decreasingAlphaRampDuration	: 0<br>
     * alphaAtZeroDuration		: 0<br>
     * isPaused				: false<br>
     * pauseTime			: 0<br>
     * </ul>
     */
    public Alpha() {
        loopCount = -1;
        mode = INCREASING_ENABLE;
        increasingAlpha = 1.0f;          // converted to seconds internally
	/*
	// Java initialize them to zero by default
        triggerTime = 0L;
        phaseDelay = 0.0f;
        increasingAlphaRamp = 0.0f;
        alphaAtOne = 0.0f;
        decreasingAlpha = 0.0f;
        decreasingAlphaRamp = 0.0f;
        alphaAtZero = 0.0f;
	*/
    }


    /**
     * This constructor takes all of the Alpha user-definable parameters.
     * @param loopCount number of times to run this alpha; a value
     * of -1 specifies that the alpha loops indefinitely
     * @param mode indicates whether the increasing alpha parameters or
     * the decreasing alpha parameters or both are active.  This parameter
     * accepts the following values, INCREASING_ENABLE or
     * DECREASING_ENABLE, which may be ORed together to specify
     * that both are active.
     * The increasing alpha parameters are increasingAlphaDuration,
     * increasingAlphaRampDuration, and alphaAtOneDuration.
     * The decreasing alpha parameters are decreasingAlphaDuration,
     * decreasingAlphaRampDuration, and alphaAtZeroDuration.
     * @param triggerTime time in milliseconds since the start time
     * that this object first triggers
     * @param phaseDelayDuration number of milliseconds to wait after
     * triggerTime before actually starting this alpha
     * @param increasingAlphaDuration period of time during which alpha goes
     * from zero to one
     * @param increasingAlphaRampDuration period of time during which
     * the alpha step size increases at the beginning of the
     * increasingAlphaDuration and, correspondingly, decreases at the end
     * of the increasingAlphaDuration. This value is clamped to half of
     * increasingAlphaDuration. NOTE: a value of zero means that the alpha
     * step size remains constant during the entire increasingAlphaDuration.
     * @param alphaAtOneDuration period of time that alpha stays at one
     * @param decreasingAlphaDuration period of time during which alpha goes
     * from one to zero
     * @param decreasingAlphaRampDuration period of time during which
     * the alpha step size increases at the beginning of the
     * decreasingAlphaDuration and, correspondingly, decreases at the end
     * of the decreasingAlphaDuration. This value is clamped to half of
     * decreasingAlphaDuration. NOTE: a value of zero means that the alpha
     * step size remains constant during the entire decreasingAlphaDuration.
     * @param alphaAtZeroDuration period of time that alpha stays at zero
     */
    public Alpha(int loopCount, int mode,
		 long triggerTime, long phaseDelayDuration,
		 long increasingAlphaDuration,
		 long increasingAlphaRampDuration,
		 long alphaAtOneDuration,
		 long decreasingAlphaDuration,
		 long decreasingAlphaRampDuration,
		 long alphaAtZeroDuration) {

	this.loopCount = loopCount;
	this.mode = mode;
	this.triggerTime = (float) triggerTime * .001f;
	phaseDelay = (float) phaseDelayDuration * .001f;

	increasingAlpha = (float) increasingAlphaDuration * .001f;
	alphaAtOne = (float)alphaAtOneDuration * .001f;
	increasingAlphaRamp = increasingAlphaRampDuration;
	incAlphaRampInternal = increasingAlphaRampDuration * .001f;
	if (incAlphaRampInternal > (0.5f * increasingAlpha)) {
	    incAlphaRampInternal = 0.5f * increasingAlpha;
	}

	decreasingAlpha = (float)decreasingAlphaDuration * .001f;
	alphaAtZero = (float)alphaAtZeroDuration * .001f;
	decreasingAlphaRamp = decreasingAlphaRampDuration;
	decAlphaRampInternal = decreasingAlphaRampDuration * .001f;
	if (decAlphaRampInternal > (0.5f * decreasingAlpha)) {
	    decAlphaRampInternal = 0.5f * decreasingAlpha;
	}
	computeStopTime();
    }


    /**
     * Constructs a new Alpha object that assumes that the mode is
     * INCREASING_ENABLE.
     *
     * @param loopCount number of times to run this alpha; a value
     * of -1 specifies that the alpha loops indefinitely.
     * @param triggerTime time in milliseconds since the start time
     * that this object first triggers
     * @param phaseDelayDuration number of milliseconds to wait after
     * triggerTime before actually starting this alpha
     * @param increasingAlphaDuration period of time during which alpha goes
     * from zero to one
     * @param increasingAlphaRampDuration period of time during which
     * the alpha step size increases at the beginning of the
     * increasingAlphaDuration and, correspondingly, decreases at the end
     * of the increasingAlphaDuration. This value is clamped to half of
     * increasingAlphaDuration. NOTE: a value of zero means that the alpha
     * step size remains constant during the entire increasingAlphaDuration.
     * @param alphaAtOneDuration period of time that alpha stays at one
     */

    public Alpha(int loopCount,
		 long triggerTime, long phaseDelayDuration,
		 long increasingAlphaDuration,
		 long increasingAlphaRampDuration,
		 long alphaAtOneDuration) {
	this(loopCount, INCREASING_ENABLE,
	     triggerTime, phaseDelayDuration,
	     increasingAlphaDuration, increasingAlphaRampDuration,
	     alphaAtOneDuration, 0, 0, 0);
    }


    /**
      *  This constructor takes only the loopCount and increasingAlphaDuration
      *  as parameters and assigns the default values to all of the other
      *  parameters.
      * @param loopCount number of times to run this alpha; a value
      * of -1 specifies that the alpha loops indefinitely
      * @param increasingAlphaDuration period of time during which alpha goes
      * from zero to one
      */
    public Alpha(int loopCount, long increasingAlphaDuration) {
        // defaults
        mode = INCREASING_ENABLE;
        increasingAlpha = (float) increasingAlphaDuration * .001f;
        this.loopCount = loopCount;

	if (loopCount >= 0) {
	    stopTime = loopCount*increasingAlpha;
	}
    }


    /**
     * Pauses this alpha object.  The current system time when this
     * method is called will be used in place of the actual current
     * time when calculating subsequent alpha values.  This has the
     * effect of freezing the interpolator at the time the method is
     * called.
     *
     * @since Java 3D 1.3
     */
    public void pause() {
	pause(J3dClock.currentTimeMillis());
    }

    /**
     * Pauses this alpha object as of the specified time.  The specified
     * time will be used in place of the actual current time when
     * calculating subsequent alpha values.  This has the effect of freezing
     * the interpolator at the specified time.  Note that specifying a
     * time in the future (that is, a time greater than
     * System.currentTimeMillis()) will cause the alpha to immediately
     * advance to that point before pausing.  Similarly, specifying a
     * time in the past (that is, a time less than
     * System.currentTimeMillis()) will cause the alpha to immediately
     * revert to that point before pausing.
     *
     * @param time the time at which to pause the alpha
     *
     * @exception IllegalArgumentException if time <= 0
     *
     * @since Java 3D 1.3
     */
    public void pause(long time) {
	if (time <= 0L) {
	    throw new IllegalArgumentException(J3dI18N.getString("Alpha0"));
	}

	paused = true;
	pauseTime = time;
	VirtualUniverse.mc.sendRunMessage(J3dThread.RENDER_THREAD);
    }

    /**
     * Resumes this alpha object.  If the alpha
     * object was paused, the difference between the current
     * time and the pause time will be used to adjust the startTime of
     * this alpha.  The equation is as follows:
     *
     * <ul>
     * <code>startTime += System.currentTimeMillis() - pauseTime</code>
     * </ul>
     *
     * Since the alpha object is no longer paused, this has the effect
     * of resuming the interpolator as of the current time.  If the
     * alpha object is not paused when this method is called, then this
     * method does nothing--the start time is not adjusted in this case.
     *
     * @since Java 3D 1.3
     */
    public void resume() {
	resume(J3dClock.currentTimeMillis());
    }

    /**
     * Resumes this alpha object as of the specified time.  If the alpha
     * object was paused, the difference between the specified
     * time and the pause time will be used to adjust the startTime of
     * this alpha.  The equation is as follows:
     *
     * <ul><code>startTime += time - pauseTime</code></ul>
     *
     * Since the alpha object is no longer paused, this has the effect
     * of resuming the interpolator as of the specified time.  If the
     * alpha object is not paused when this method is called, then this
     * method does nothing--the start time is not adjusted in this case.
     *
     * @param time the time at which to resume the alpha
     *
     * @exception IllegalArgumentException if time <= 0
     *
     * @since Java 3D 1.3
     */
    public void resume(long time) {
	if (time <= 0L) {
	    throw new IllegalArgumentException(J3dI18N.getString("Alpha0"));
	}

	if (paused) {
	    long newStartTime = startTime + time - pauseTime;
	    paused = false;
	    pauseTime = 0L;
	    setStartTime(newStartTime);
	}
    }

    /**
     * Returns true if this alpha object is paused.
     * @return true if this alpha object is paused, false otherwise
     *
     * @since Java 3D 1.3
     */
    public boolean isPaused() {
	return paused;
    }

    /**
     * Returns the time at which this alpha was paused.
     * @return the pause time; returns 0 if this alpha is not paused
     *
     * @since Java 3D 1.3
     */
    public long getPauseTime() {
	return pauseTime;
    }


    /**
     * This method returns a value between 0.0 and 1.0 inclusive,
     * based on the current time and the time-to-alpha parameters
     * established for this alpha.  If this alpha object is paused,
     * the value will be based on the pause time rather than the
     * current time.
     * This method will return the starting alpha value if the alpha
     * has not yet started (that is, if the current time is less
     * than startTime + triggerTime + phaseDelayDuration). This
     * method will return the ending alpha value if the alpha has
     * finished (that is, if the loop count has expired).
     *
     * @return a value between 0.0 and 1.0 based on the current time
     */
    public float value() {
	long currentTime = paused ? pauseTime : J3dClock.currentTimeMillis();
	return this.value(currentTime);
    }

    /**
     * This method returns a value between 0.0 and 1.0 inclusive,
     * based on the specified time and the time-to-alpha parameters
     * established for this alpha.
     * This method will return the starting alpha value if the alpha
     * has not yet started (that is, if the specified time is less
     * than startTime + triggerTime + phaseDelayDuration). This
     * method will return the ending alpha value if the alpha has
     * finished (that is, if the loop count has expired).
     *
     * @param atTime The time for which we wish to compute alpha
     * @return a value between 0.0 and 1.0 based on the specified time
     */
    public float value(long atTime) {
	float interpolatorTime
	  = (float)(atTime  - startTime) * .001f; // startTime is in millisec
	float alpha, a1, a2, dt, alphaRampDuration;

	//	System.err.println("alpha mode: " + mode);

	// If non-looping and before start
	//	if ((loopCount != -1) &&
	//	    interpolatorTime <= ( triggerTime +  phaseDelay)) {
	//
	//	    if (( mode & INCREASING_ENABLE ) == 0 &&
	//		( mode & DECREASING_ENABLE) != 0)
	//		alpha = 1.0f;
	//	    else
	//		alpha = 0.0f;
	//	    return alpha;
	//	}


	//  Case of {constantly} moving forward, snap back, forward again
	if (( mode & INCREASING_ENABLE ) != 0 &&
	    ( mode & DECREASING_ENABLE) == 0) {

               if(interpolatorTime <= (triggerTime + phaseDelay))
                      return 0.0f;

               if((loopCount != -1) && (interpolatorTime >= stopTime))
                      return 1.0f;

	    //  Constant velocity case
	    if (incAlphaRampInternal == 0.0f) {

		alpha = mfmod((interpolatorTime -  triggerTime -  phaseDelay) +
			      6.0f*( increasingAlpha +  alphaAtOne),
			      (increasingAlpha + alphaAtOne))/ increasingAlpha;

		if ( alpha > 1.0f)  alpha = 1.0f;
		return alpha;
	    }

	    //  Ramped velocity case
	    alphaRampDuration =  incAlphaRampInternal;

	    dt = mfmod((interpolatorTime -  triggerTime -  phaseDelay) +
		       6.0f*( increasingAlpha +  alphaAtOne),
		       ( increasingAlpha +  alphaAtOne));
	    if (dt >=  increasingAlpha) {  alpha = 1.0f; return alpha; }

		// Original equation kept to help understand
		// computation logic - simplification saves
 		// a multiply and an add
		// a1 = 1.0f/(alphaRampDuration*alphaRampDuration +
		//	   ( increasingAlpha - 2*alphaRampDuration)*
		//	   alphaRampDuration);

		a1 = 1.0f/(increasingAlpha * alphaRampDuration -
			alphaRampDuration * alphaRampDuration);

	    if (dt < alphaRampDuration) {
		alpha = 0.5f*a1*dt*dt;
	    } else if (dt <  increasingAlpha - alphaRampDuration) {
		alpha = 0.5f*a1*alphaRampDuration*
		    alphaRampDuration +
		    (dt - alphaRampDuration)*a1*
		    alphaRampDuration;
	    } else {
		alpha = a1*alphaRampDuration*alphaRampDuration +
		    ( increasingAlpha - 2.0f*alphaRampDuration)*a1*
		    alphaRampDuration -
		    0.5f*a1*( increasingAlpha - dt)*
		    ( increasingAlpha - dt);
	    }
	    return alpha;

	} else


	    // Case of {constantly} moving backward, snap forward, backward
	    // again
	    if (( mode & INCREASING_ENABLE ) == 0 &&
		( mode & DECREASING_ENABLE) != 0) {

		// If non-looping and past end
		//		if ((loopCount != -1)
		//		    && (interpolatorTime
		//			>= (triggerTime + phaseDelay + decreasingAlpha))) {
		//		    alpha = 0.0f;
		//		    return alpha;
		//		}

                if(interpolatorTime <= (triggerTime + phaseDelay))
                      return 1.0f;

                if((loopCount != -1) && (interpolatorTime >= stopTime) )
                      return 0.0f;



		//  Constant velocity case
		if (decAlphaRampInternal == 0.0f) {
		    alpha = mfmod((interpolatorTime -  triggerTime -
				   phaseDelay) +
				  6.0f*( decreasingAlpha + alphaAtZero),
				  (decreasingAlpha + alphaAtZero))/ decreasingAlpha;
		    if ( alpha > 1.0f) {  alpha = 0.0f; return alpha; }
		    alpha = 1.0f -  alpha;
		    return alpha;
		}

		//  Ramped velocity case
		alphaRampDuration =  decAlphaRampInternal;

		dt = mfmod((interpolatorTime -  triggerTime -  phaseDelay) +
			   6.0f*( decreasingAlpha +  alphaAtZero),
			   ( decreasingAlpha +  alphaAtZero));
		if (dt >=  decreasingAlpha) {  alpha = 0.0f; return alpha; }

		// Original equation kept to help understand
		// computation logic - simplification saves
 		// a multiply and an add
		// a1 = 1.0f/(alphaRampDuration*alphaRampDuration +
		//	   ( decreasingAlpha - 2*alphaRampDuration)*
		//	   alphaRampDuration);

		a1 = 1.0f/(decreasingAlpha * alphaRampDuration -
			alphaRampDuration * alphaRampDuration);

		if (dt < alphaRampDuration) {
		    alpha = 0.5f*a1*dt*dt;
		} else if (dt <  decreasingAlpha - alphaRampDuration) {
		    alpha = 0.5f*a1*alphaRampDuration*
			alphaRampDuration +
			(dt - alphaRampDuration)*a1*
			alphaRampDuration;
		} else {
		    alpha = a1*alphaRampDuration*alphaRampDuration +
			( decreasingAlpha - 2.0f*alphaRampDuration)*a1*
			alphaRampDuration -
			0.5f*a1*( decreasingAlpha - dt)*
			( decreasingAlpha - dt);
		}
		alpha = 1.0f -  alpha;
		return alpha;

	    } else


		//  Case of {osscilating} increasing and decreasing alpha
		if (( mode & INCREASING_ENABLE) != 0 &&
		    ( mode & DECREASING_ENABLE) != 0) {

		    // If non-looping and past end
		  //		    if ((loopCount != -1) &&
		  //			(interpolatorTime >=
		  //			 (triggerTime +  phaseDelay +  increasingAlpha +
		  //			  alphaAtOne +  decreasingAlpha))) {
		  //			alpha = 0.0f;
		  //			return alpha;
		  //  }


		    // If non-looping and past end, we always end up at zero since
		    // decreasing alpha has been requested.
		    if(interpolatorTime <= (triggerTime + phaseDelay))
                         return 0.0f;

		    if( (loopCount != -1) && (interpolatorTime >= stopTime))
                         return 0.0f;

		    //  Constant velocity case
		    if (incAlphaRampInternal == 0.0f
			&& decAlphaRampInternal == 0.0f) {
			dt = mfmod(interpolatorTime - triggerTime - phaseDelay +
				   6.0f*(increasingAlpha + alphaAtOne +
					 decreasingAlpha + alphaAtZero),
				   increasingAlpha + alphaAtOne +
				   decreasingAlpha + alphaAtZero);
			alpha = dt / increasingAlpha;
			if ( alpha < 1.0f) return alpha;
			// sub all increasing alpha time
			dt -=  increasingAlpha;
			if (dt <  alphaAtOne) {  alpha = 1.0f; return alpha; }
			// sub out alpha @ 1 time
			dt -=  alphaAtOne;
			alpha = dt/ decreasingAlpha;
			if ( alpha < 1.0f)  alpha = 1.0f -  alpha;
			else  alpha = 0.0f;
			return alpha;
		    }

		    //  Ramped velocity case
		    alphaRampDuration =  incAlphaRampInternal;

                    // work around for bug 4308308
                    if (alphaRampDuration == 0.0f)
                        alphaRampDuration = .00001f;

		    dt = mfmod(interpolatorTime -  triggerTime -  phaseDelay +
			       6.0f*( increasingAlpha +  alphaAtOne +
				      decreasingAlpha +  alphaAtZero),
			       increasingAlpha +  alphaAtOne +
			       decreasingAlpha +  alphaAtZero);
		    if (dt <=  increasingAlpha) {

			// Original equation kept to help understand
			// computation logic - simplification saves
 			// a multiply and an add
			// a1 = 1.0f/(alphaRampDuration*alphaRampDuration +
			//	   ( increasingAlpha - 2*alphaRampDuration)*
			//	   alphaRampDuration);

			a1 = 1.0f/(increasingAlpha * alphaRampDuration -
				alphaRampDuration * alphaRampDuration);

			if (dt < alphaRampDuration) {
			    alpha = 0.5f*a1*dt*dt;
			} else if (dt <  increasingAlpha - alphaRampDuration) {
			    alpha = 0.5f*a1*alphaRampDuration*
				alphaRampDuration +
				(dt - alphaRampDuration)*a1*
				alphaRampDuration;
			} else {
			    alpha = a1*alphaRampDuration*alphaRampDuration+
				( increasingAlpha - 2.0f*alphaRampDuration)*a1*
				alphaRampDuration -
				0.5f*a1*( increasingAlpha - dt)*
				( increasingAlpha - dt);
			}
			return alpha;
		    }
		    else if (dt <=  increasingAlpha +  alphaAtOne) {
			alpha = 1.0f; return alpha;
		    }
		    else if (dt >=  increasingAlpha +  alphaAtOne +  decreasingAlpha) {
			alpha = 0.0f; return alpha;
		    }
		    else {
			dt -=  increasingAlpha + alphaAtOne;

			alphaRampDuration =  decAlphaRampInternal;

                        // work around for bug 4308308
                        if (alphaRampDuration == 0.0f)
                            alphaRampDuration = .00001f;

			// Original equation kept to help understand
			// computation logic - simplification saves
			// a multiply and an add
			// a1 = 1.0f/(alphaRampDuration*alphaRampDuration +
			//	   ( decreasingAlpha - 2*alphaRampDuration)*
			//	   alphaRampDuration);

			a1 = 1.0f/(decreasingAlpha * alphaRampDuration -
				alphaRampDuration * alphaRampDuration);

			if (dt < alphaRampDuration) {
			    alpha = 0.5f*a1*dt*dt;
			} else if (dt <  decreasingAlpha - alphaRampDuration) {
			    alpha = 0.5f*a1*alphaRampDuration*
				alphaRampDuration +
				(dt - alphaRampDuration)*a1*
				alphaRampDuration;
			} else {
			    alpha =
				a1*alphaRampDuration*alphaRampDuration +
				(decreasingAlpha - 2.0f*alphaRampDuration)*a1*
				alphaRampDuration -
				0.5f*a1*( decreasingAlpha - dt)*
				(decreasingAlpha - dt);
			}
			alpha = 1.0f -  alpha;
			return alpha;
		    }

		}
	return 0.0f;
    }

    float mfmod(float a, float b) {
	float fm, ta = (a), tb = (b);
	int fmint;
	if (tb < 0.0f) tb = -tb;
	if (ta < 0.0f) ta = -ta;

	fmint =(int)( ta/tb);
	fm = ta - (float)fmint * tb;

	if ((a) < 0.0f) return ((b) - fm);
	else return fm;
    }

    /**
      * Retrieves this alpha's startTime, the base
      * for all relative time specifications; the default value
      * for startTime is the system start time.
      * @return this alpha's startTime.
      */
    public long getStartTime() {
	return this.startTime;
    }

    /**
     * Sets this alpha's startTime to that specified in the argument;
     * startTime sets the base (or zero) for all relative time
     * computations; the default value for startTime is the system
     * start time.
     * @param startTime the new startTime value
     */
    public void setStartTime(long startTime) {
	this.startTime = startTime;
	// This is used for passive wakeupOnElapsedFrame in
	// Interpolator to restart behavior after alpha.finished()
	VirtualUniverse.mc.sendRunMessage(J3dThread.RENDER_THREAD);
    }

    /**
      * Retrieves this alpha's loopCount.
      * @return this alpha's loopCount.
      */
    public int getLoopCount() {
	return this.loopCount;
    }

    /**
      * Set this alpha's loopCount to that specified in the argument.
      * @param loopCount the new loopCount value
      */
    public void setLoopCount(int loopCount) {
	this.loopCount = loopCount;
	computeStopTime();
	VirtualUniverse.mc.sendRunMessage(J3dThread.RENDER_THREAD);
    }

    /**
      * Retrieves this alpha's mode.
      * @return this alpha's mode: any combination of
      * INCREASING_ENABLE and DECREASING_ENABLE
      */
    public int getMode() {
	return this.mode;
    }

    /**
      * Set this alpha's mode to that specified in the argument.
     * @param mode indicates whether the increasing alpha parameters or
     * the decreasing alpha parameters or both are active.  This parameter
     * accepts the following values, INCREASING_ENABLE or
     * DECREASING_ENABLE, which may be ORed together to specify
     * that both are active.
     * The increasing alpha parameters are increasingAlphaDuration,
     * increasingAlphaRampDuration, and alphaAtOneDuration.
     * The decreasing alpha parameters are decreasingAlphaDuration,
     * decreasingAlphaRampDuration, and alphaAtZeroDuration.
      */
    public void setMode(int mode) {
	this.mode = mode;
	computeStopTime();
	VirtualUniverse.mc.sendRunMessage(J3dThread.RENDER_THREAD);
    }

    /**
      * Retrieves this alpha's triggerTime.
      * @return this alpha's triggerTime.
      */
    public long getTriggerTime() {
	return (long) (this.triggerTime * 1000f);
    }

    /**
      * Set this alpha's triggerTime to that specified in the argument.
      * @param triggerTime  the new triggerTime
      */
    public void setTriggerTime(long triggerTime) {
	this.triggerTime = (float) triggerTime * .001f;
	computeStopTime();
	VirtualUniverse.mc.sendRunMessage(J3dThread.RENDER_THREAD);
    }

    /**
      * Retrieves this alpha's phaseDelayDuration.
      * @return this alpha's phaseDelayDuration.
      */
    public long getPhaseDelayDuration() {
	return (long)(this.phaseDelay * 1000f);
    }

    /**
      * Set this alpha's phaseDelayDuration to that specified in
      * the argument.
      * @param phaseDelayDuration  the new phaseDelayDuration
      */
    public void setPhaseDelayDuration(long phaseDelayDuration) {
	this.phaseDelay = (float) phaseDelayDuration * .001f;
	computeStopTime();
	VirtualUniverse.mc.sendRunMessage(J3dThread.RENDER_THREAD);
    }

    /**
      * Retrieves this alpha's increasingAlphaDuration.
      * @return this alpha's increasingAlphaDuration.
      */
    public long getIncreasingAlphaDuration() {
	return (long)(this.increasingAlpha * 1000f);
    }

    /**
      * Set this alpha's increasingAlphaDuration to that specified in
      * the argument.
      * @param increasingAlphaDuration  the new increasingAlphaDuration
      */
    public void setIncreasingAlphaDuration(long increasingAlphaDuration) {
	this.increasingAlpha = (float) increasingAlphaDuration * .001f;
	computeStopTime();
	VirtualUniverse.mc.sendRunMessage(J3dThread.RENDER_THREAD);
    }

    /**
      * Retrieves this alpha's increasingAlphaRampDuration.
      * @return this alpha's increasingAlphaRampDuration.
      */
    public long getIncreasingAlphaRampDuration() {
	return increasingAlphaRamp;
    }

    /**
      * Set this alpha's increasingAlphaRampDuration to that specified
      * in the argument.
      * @param increasingAlphaRampDuration  the new increasingAlphaRampDuration
      */
    public void setIncreasingAlphaRampDuration(long increasingAlphaRampDuration) {
	increasingAlphaRamp = increasingAlphaRampDuration;
	incAlphaRampInternal = (float) increasingAlphaRampDuration * .001f;
	if (incAlphaRampInternal > (0.5f * increasingAlpha)) {
	    incAlphaRampInternal = 0.5f * increasingAlpha;
	}
	VirtualUniverse.mc.sendRunMessage(J3dThread.RENDER_THREAD);
    }

    /**
      * Retrieves this alpha's alphaAtOneDuration.
      * @return this alpha's alphaAtOneDuration.
      */
    public long getAlphaAtOneDuration() {
	return (long)(this.alphaAtOne * 1000f);
    }

    /**
     * Set this alpha object's alphaAtOneDuration to the specified
     * value.
     * @param alphaAtOneDuration  the new alphaAtOneDuration
     */
    public void setAlphaAtOneDuration(long alphaAtOneDuration) {
	this.alphaAtOne = (float) alphaAtOneDuration * .001f;
	computeStopTime();
	VirtualUniverse.mc.sendRunMessage(J3dThread.RENDER_THREAD);
    }

    /**
      * Retrieves this alpha's decreasingAlphaDuration.
      * @return this alpha's decreasingAlphaDuration.
      */
    public long getDecreasingAlphaDuration() {
	return (long)(this.decreasingAlpha * 1000f);
    }

    /**
      * Set this alpha's decreasingAlphaDuration to that specified in
      * the argument.
      * @param decreasingAlphaDuration  the new decreasingAlphaDuration
      */
    public void setDecreasingAlphaDuration(long decreasingAlphaDuration) {
	this.decreasingAlpha = (float) decreasingAlphaDuration * .001f;
	computeStopTime();
	VirtualUniverse.mc.sendRunMessage(J3dThread.RENDER_THREAD);
    }

    /**
      * Retrieves this alpha's decreasingAlphaRampDuration.
      * @return this alpha's decreasingAlphaRampDuration.
      */
    public long getDecreasingAlphaRampDuration() {
	return decreasingAlphaRamp;
    }

    /**
      * Set this alpha's decreasingAlphaRampDuration to that specified
      * in the argument.
      * @param decreasingAlphaRampDuration  the new decreasingAlphaRampDuration
      */
    public void setDecreasingAlphaRampDuration(long decreasingAlphaRampDuration) {
	decreasingAlphaRamp = decreasingAlphaRampDuration;
	decAlphaRampInternal = (float) decreasingAlphaRampDuration * .001f;
	if (decAlphaRampInternal > (0.5f * decreasingAlpha)) {
	    decAlphaRampInternal = 0.5f * decreasingAlpha;
	}
	VirtualUniverse.mc.sendRunMessage(J3dThread.RENDER_THREAD);
    }

    /**
      * Retrieves this alpha's alphaAtZeroDuration.
      * @return this alpha's alphaAtZeroDuration.
      */
    public long getAlphaAtZeroDuration() {
	return (long)(this.alphaAtZero * 1000f);
    }

    /**
     * Set this alpha object's alphaAtZeroDuration to the specified
     * value.
     * @param alphaAtZeroDuration  the new alphaAtZeroDuration
     */
    public void setAlphaAtZeroDuration(long alphaAtZeroDuration) {
	this.alphaAtZero = (float) alphaAtZeroDuration * .001f;
	computeStopTime();
	VirtualUniverse.mc.sendRunMessage(J3dThread.RENDER_THREAD);
    }

    /**
     * Query to test if this alpha object is past its activity window,
     * that is, if it has finished looping.
     * @return true if no longer looping, false otherwise
     */
    public boolean finished() {
	long currentTime = paused ? pauseTime : J3dClock.currentTimeMillis();
	return ((loopCount != -1) &&
 	        ((float)(currentTime - startTime) * .001f > stopTime));
    }

    final private void computeStopTime() {
        if (loopCount >= 0) {
	    float sum = 0;
	    if (( mode & INCREASING_ENABLE ) != 0) {
		sum = increasingAlpha+alphaAtOne;
	    }
	    if ((mode & DECREASING_ENABLE) != 0) {
		sum += decreasingAlpha+alphaAtZero;
	    }
	    stopTime = this.triggerTime + phaseDelay + loopCount*sum;
	} else {
	    stopTime = 0;
	}
    }

    /**
     * This internal method returns a clone of the Alpha
     *
     * @return a duplicate of this Alpha
     */
    Alpha cloneAlpha() {
      Alpha a = new Alpha();
      a.setStartTime(getStartTime());
      a.setLoopCount(getLoopCount());
      a.setMode(getMode());
      a.setTriggerTime(getTriggerTime());
      a.setPhaseDelayDuration(getPhaseDelayDuration());
      a.setIncreasingAlphaDuration(getIncreasingAlphaDuration());
      a.setIncreasingAlphaRampDuration(getIncreasingAlphaRampDuration());
      a.setAlphaAtOneDuration(getAlphaAtOneDuration());
      a.setDecreasingAlphaDuration(getDecreasingAlphaDuration());
      a.setDecreasingAlphaRampDuration(getDecreasingAlphaRampDuration());
      a.setAlphaAtZeroDuration(getAlphaAtZeroDuration());
      return a;
    }

    static {
        VirtualUniverse.loadLibraries();
    }

}