/*
* Copyright (c) 2002-@year@, University of Maryland
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are permitted provided
* that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice, this list of conditions
* and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright notice, this list of conditions
* and the following disclaimer in the documentation and/or other materials provided with the
* distribution.
*
* Neither the name of the University of Maryland nor the names of its contributors may be used to
* endorse or promote products derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
* PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
* TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* Piccolo was written at the Human-Computer Interaction Laboratory www.cs.umd.edu/hcil by Jesse Grosjean
* under the supervision of Ben Bederson. The Piccolo website is www.cs.umd.edu/hcil/piccolo.
*/
package edu.umd.cs.piccolo.activities;
/**
* <b>PInterpolatingActivity</b> interpolates between two states (source and
* destination) over the duration of the activity. The interpolation can be
* either linear or slow- in, slow-out.
* <P>
* The mode determines how the activity interpolates between the two states. The
* default mode interpolates from source to destination, but you can also go
* from destination to source, and from source to destination to source.
* <P>
* A loopCount of greater then one will make the activity reschedule itself when
* it has finished. This makes the activity loop between the two states.
* <P>
* @version 1.0
* @author Jesse Grosjean
*/
public class PInterpolatingActivity extends PActivity {
public static final int SOURCE_TO_DESTINATION = 1;
public static final int DESTINATION_TO_SOURCE = 2;
public static final int SOURCE_TO_DESTINATION_TO_SOURCE = 3;
private int mode;
private boolean slowInSlowOut;
private int loopCount;
private boolean firstLoop;
public PInterpolatingActivity(long duration, long stepRate) {
this(duration, stepRate, 1, PInterpolatingActivity.SOURCE_TO_DESTINATION);
}
public PInterpolatingActivity(long duration, long stepRate, int loopCount, int mode) {
this(duration, stepRate, System.currentTimeMillis(), loopCount, mode);
}
/**
* Create a new PInterpolatingActivity.
* <P>
* @param duration the length of one loop of the activity
* @param stepRate the amount of time between steps of the activity
* @param startTime the time (relative to System.currentTimeMillis()) that
* this activity should start.
* @param loopCount number of times the activity should reschedule itself
* @param mode defines how the activity interpolates between states
*/
public PInterpolatingActivity(long duration, long stepRate, long startTime, int loopCount, int mode) {
super(duration, stepRate, startTime);
this.loopCount = loopCount;
this.mode = mode;
slowInSlowOut = true;
firstLoop = true;
}
/**
* Set the amount of time that this activity should take to complete,
* after the startStepping method is called. The duration must be greater
* then zero so that the interpolation value can be computed.
*/
public void setDuration(long aDuration) {
if (aDuration <= 0)
throw new IllegalArgumentException("Duration for PInterpolatingActivity must be greater then 0");
super.setDuration(aDuration);
}
//****************************************************************
// Basics.
//****************************************************************
/**
* Return the mode that defines how the activity interpolates between
* states.
*/
public int getMode() {
return mode;
}
/**
* Set the mode that defines how the activity interpolates between states.
*/
public void setMode(int mode) {
this.mode = mode;
}
/**
* Return the number of times the activity should automatically reschedule
* itself after it has finished.
*/
public int getLoopCount() {
return loopCount;
}
/**
* Set the number of times the activity should automatically reschedule
* itself after it has finished.
*/
public void setLoopCount(int loopCount) {
this.loopCount = loopCount;
}
/**
* Return true if the activity is executing its first loop. Subclasses
* normally initialize their source state on the first loop.
*/
public boolean getFirstLoop() {
return firstLoop;
}
/**
* Set if the activity is executing its first loop. Subclasses normally
* initialize their source state on the first loop. This method will rarely
* need to be called, unless your are reusing activities.
*/
public void setFirstLoop(boolean firstLoop) {
this.firstLoop = firstLoop;
}
public boolean getSlowInSlowOut() {
return slowInSlowOut;
}
public void setSlowInSlowOut(boolean isSlowInSlowOut) {
slowInSlowOut = isSlowInSlowOut;
}
//****************************************************************
// Stepping - Instead of overriding the step methods subclasses
// of this activity will normally override setRelativeTargetValue().
// This method will be called for every step of the activity with
// a value ranging from 0,0 (for the first step) to 1.0 (for the
// final step). See PTransformActivity for an example.
//****************************************************************
protected void activityStarted() {
super.activityStarted();
setRelativeTargetValueAdjustingForMode(0);
}
protected void activityStep(long elapsedTime) {
super.activityStep(elapsedTime);
float t = elapsedTime / (float) getDuration();
t = Math.min(1, t);
t = Math.max(0, t);
if (getSlowInSlowOut()) {
t = computeSlowInSlowOut(t);
}
setRelativeTargetValueAdjustingForMode(t);
}
protected void activityFinished() {
setRelativeTargetValueAdjustingForMode(1);
super.activityFinished();
PActivityScheduler scheduler = getActivityScheduler();
if (loopCount > 1) {
if (loopCount != Integer.MAX_VALUE) loopCount--;
firstLoop = false;
setStartTime(scheduler.getRoot().getGlobalTime());
scheduler.addActivity(this);
}
}
/**
* Stop this activity immediately, and remove it from the activity
* scheduler. If this activity is currently running then stoppedStepping
* will be called after it has been removed from the activity scheduler.
*/
public void terminate() {
loopCount = 0; // set to zero so that we don't reschedule self.
super.terminate();
}
/**
* Subclasses should override this method and set the value on their
* target (the object that they are modifying) accordingly.
*/
public void setRelativeTargetValue(float zeroToOne) {
}
public float computeSlowInSlowOut(float zeroToOne) {
if (zeroToOne < 0.5) {
return 2.0f * zeroToOne * zeroToOne;
} else {
float complement = 1.0f - zeroToOne;
return 1.0f - (2.0f * complement * complement);
}
}
protected void setRelativeTargetValueAdjustingForMode(float zeroToOne) {
switch (mode) {
case SOURCE_TO_DESTINATION:
break;
case DESTINATION_TO_SOURCE:
zeroToOne = 1 - zeroToOne;
break;
case SOURCE_TO_DESTINATION_TO_SOURCE:
if (zeroToOne <= 0.5) {
zeroToOne *= 2;
} else {
zeroToOne = 1 - ((zeroToOne - 0.5f) * 2);
}
break;
}
setRelativeTargetValue(zeroToOne);
}
//****************************************************************
// Debugging - methods for debugging
//****************************************************************
/**
* Returns a string representing the state of this node. This method is
* intended to be used only for debugging purposes, and the content and
* format of the returned string may vary between implementations. The
* returned string may be empty but may not be <code>null</code>.
*
* @return a string representation of this node's state
*/
protected String paramString() {
StringBuffer result = new StringBuffer();
if (slowInSlowOut) {
result.append("slowinSlowOut,");
}
result.append(super.paramString());
return result.toString();
}
}