I think there's an error in KeyframeController, let me explain: when repeatType = RT_WRAP and curTime > getMaxTime(), wrapping is performed by resetting time to getMinTime()-1 in order to search next frame. I think this is incorrect because I expect wrapping to work in such a way that jumping over getMaxTime() takes me to curTime = getMinTime() + ( curTime - getMaxTime() ) which is a truly "circular" behavior.
The same applies to the case wher curTime < getMinTime(), which takes me to another problem: it is possible to have getMinTime() > getMaxTime() (making all RT_WRAP tests to behave strangely) by calling setNewAnimationTimes with newBeginTime being greater than newEndTime, I don't know what this is allowed, but I think that in addition to set movingForward = false it is necessary to set maxTime and minTime with newBeginTime and newEndTime respectively. The attached code fixes both problems:
Note: I think that something similar applies when having repeatType = RT_CYCLE, but I have not the time to fix it right now.
[pre]
/
* Sets the new animation boundaries for this controller. This will start at
* newBeginTime and proceed in the direction of newEndTime (either forwards
* or backwards). If both are the same, then the animation is set to their
* time and turned off, otherwise the animation is turned on to start the
* animation acording to the repeat type. If either BeginTime or EndTime are
* invalid times (less than 0 or greater than the maximum set keyframe time)
* then a warning is set and nothing happens. <br>
* It is suggested that this function be called if new animation boundaries
* need to be set, instead of setMinTime and setMaxTime directly.
*
* @param newBeginTime
* The starting time
* @param newEndTime
* The ending time
*/
public void setNewAnimationTimes(float newBeginTime, float newEndTime) {
if (isSmooth) return;
if (newBeginTime < 0
|| newBeginTime > ((PointInTime) keyframes
.get(keyframes.size() - 1)).time) {
LoggingSystem.getLogger().log(Level.WARNING,
"Attempt to set invalid begintime:" + newBeginTime);
return;
}
if (newEndTime < 0
|| newEndTime > ((PointInTime) keyframes
.get(keyframes.size() - 1)).time) {
LoggingSystem.getLogger().log(Level.WARNING,
"Attempt to set invalid endtime:" + newEndTime);
return;
}
if ( newBeginTime > newEndTime ) {
setMinTime(newEndTime);
setMaxTime(newBeginTime);
movingForward = false;
curTime = newEndTime;
} else {
setMinTime(newBeginTime);
setMaxTime(newEndTime);
movingForward = true;
curTime = newBeginTime;
}
if (newBeginTime == newEndTime) {
update(0);
setActive(false);
} else
setActive(true);
}
/
* This is used by update(float). It calculates PointInTime
* <code>before</code> and <code>after</code> as well as makes
* adjustments on what to do when <code>curTime</code> is beyond the
* MinTime and MaxTime bounds
*/
private void findFrame() {
// If we're in our special wrapping case then just ignore changing
// frames. Once we get back into the actual series we'll revert back
// to the normal process
if ((curTime < getMinTime()) && (nextFrame < curFrame)) {
return;
}
// Update the rest to maintain our new nextFrame marker as one infront
// of the curFrame in all cases. The wrap case is where the real work
// is done.
if (curTime > this.getMaxTime()) {
if (isSmooth) {
swapKeyframeSets();
isSmooth = false;
curTime = tempNewBeginTime;
curFrame = 0;
nextFrame = 1;
setNewAnimationTimes(tempNewBeginTime, tempNewEndTime);
return;
}
if (this.getRepeatType() == Controller.RT_WRAP) {
float delta = curTime - this.getMaxTime();
curTime = this.getMinTime() + delta;
for (curFrame = 0; curFrame < keyframes.size() - 1; curFrame++) {
if (curTime <= ((PointInTime) keyframes.get(curFrame)).time)
break;
}
curFrame–;
return;
} else if (this.getRepeatType() == Controller.RT_CLAMP) {
return;
} else { // Then assume it's RT_CYCLE
movingForward = false;
curTime = this.getMaxTime();
}
} else if (curTime < this.getMinTime()) {
if (this.getRepeatType() == Controller.RT_WRAP) {
float delta = this.getMinTime() - curTime;
curTime = this.getMaxTime() - delta;
curFrame = 0;
} else if (this.getRepeatType() == Controller.RT_CLAMP) {
return;
} else { // Then assume it's RT_CYCLE
movingForward = true;
curTime = this.getMinTime();
}
}
nextFrame = curFrame+1;
if (curTime > ((PointInTime) keyframes.get(curFrame)).time) {
if (curTime < ((PointInTime) keyframes.get(curFrame + 1)).time) {
nextFrame = curFrame+1;
return;
}
else {
for (; curFrame < keyframes.size() - 1; curFrame++) {
if (curTime <= ((PointInTime) keyframes.get(curFrame + 1)).time) {
nextFrame = curFrame+1;
return;
}
}
// This -should- be unreachable because of the above
curTime = this.getMinTime();
curFrame = 0;
nextFrame = curFrame+1;
return;
}
} else {
for (; curFrame >= 0; curFrame–) {
if (curTime >= ((PointInTime) keyframes.get(curFrame)).time) {
nextFrame = curFrame+1;
return;
}
}
// This should be unreachable because curTime>=0 and
// keyframes[0].time=0;
curFrame = 0;
nextFrame = curFrame+1;
return;
}
}
[/pre]
Should be fixed, use KeyframeController.setBlendTime(0)