//
// AnimationControlJ3D.java
//
/*
VisAD system for interactive analysis and visualization of numerical
data. Copyright (C) 1996 - 2017 Bill Hibbard, Curtis Rueden, Tom
Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
Tommy Jasmin.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library 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
Library General Public License for more details.
You should have received a copy of the GNU Library General Public
License along with this library; if not, write to the Free
Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
MA 02111-1307, USA
*/
package visad.java3d;
import java.rmi.RemoteException;
import java.util.Enumeration;
import java.util.StringTokenizer;
import javax.media.j3d.Behavior;
import javax.media.j3d.BoundingSphere;
import javax.media.j3d.BranchGroup;
import javax.media.j3d.WakeupOnElapsedTime;
import javax.vecmath.Point3d;
import visad.AnimationControl;
import visad.AnimationSetControl;
import visad.Control;
import visad.DataDisplayLink;
import visad.DataRenderer;
import visad.DisplayException;
import visad.RealType;
import visad.Set;
import visad.ToggleControl;
import visad.VisADError;
import visad.VisADException;
import visad.browser.Convert;
/**
AnimationControlJ3D is the VisAD class for controlling Animation
display scalars under Java3D.<P>
WLH - manipulate a list of Switch nodes in scene graph.<P>
*/
public class AnimationControlJ3D extends AVControlJ3D
implements AnimationControl {
private static final long serialVersionUID = 7763197458917167330L;
private static final long DEFAULT_DWELL = 500;
protected int current = 0;//DML: made protected so subclass can use it.
private boolean direction; // true = forward
private long step = DEFAULT_DWELL; // time in milliseconds between animation steps
private WakeupOnElapsedTime[] stepValues; // times in milliseconds between animation steps
private transient AnimationSetControl animationSet;
private ToggleControl animate;
private RealType real;
private boolean computeSet = true;
/** Behavior for initiating time stepping. */
private transient Behavior steppingBehaviour;
public AnimationControlJ3D(DisplayImplJ3D d, RealType r) {
super(d);
real = r;
current = 0;
direction = true;
animationSet = new AnimationSetControl(d, this);
// initialize the stepValues array
stepValues = new WakeupOnElapsedTime[]{
new WakeupOnElapsedTime(DEFAULT_DWELL)
};
try {
Set set = animationSet.getSet();
if (set != null)
stepValues = new WakeupOnElapsedTime[set.getLength()];
} catch (VisADException v) {
}
for (int i = 0; i < stepValues.length; i++) {
stepValues[i] = new WakeupOnElapsedTime(DEFAULT_DWELL);
}
d.addControl(animationSet);
animate = new ToggleControl(d, this);
d.addControl(animate);
try {
animate.setOn(false);
} catch (VisADException v) {
} catch (RemoteException v) {
}
// add stepping behavior near the root of the scene graph
steppingBehaviour = new TakeStepBehavior();
BranchGroup bg = new BranchGroup();
bg.addChild(steppingBehaviour);
DisplayRendererJ3D rend = (DisplayRendererJ3D) d.getDisplayRenderer();
BranchGroup root = rend.getRoot();
root.addChild(bg);
}
AnimationControlJ3D() {
this(null, null);
}
public int getCurrent() {
return current;
}
/** set the current ordinal step number = c */
public void setCurrent(int c)
throws VisADException, RemoteException {
if (animationSet != null) {
current = animationSet.clipCurrent(c);
init();
}
else {
current = 0;
}
// WLH 5 May 2000
// changeControl(true);
changeControl(false);
}
/** set the current step by the value of the RealType
mapped to Display.Animation */
public void setCurrent(double value)
throws VisADException, RemoteException {
if (animationSet != null) {
current = animationSet.getIndex(value);
init();
}
else {
current = 0;
}
// WLH 5 May 2000
// changeControl(true);
changeControl(false);
}
/**
* Set the animation direction.
*
* @param dir true for forward, false for backward
*
* @throws VisADException Couldn't create necessary VisAD object. The
* direction remains unchanged.
* @throws RemoteException Java RMI exception
*/
public void setDirection(boolean dir)
throws VisADException, RemoteException {
direction = dir;
// WLH 5 May 2000
// changeControl(true);
changeControl(false);
}
/** Get the animation direction.
*
* @return true for forward, false for backward
*/
public boolean getDirection()
{
return direction;
}
/**
* Return the dwell time for the current step
*/
public long getStep() {
if (stepValues == null || current < 0 ||
stepValues.length <= current) return DEFAULT_DWELL;
else return stepValues[current].getElapsedFrameTime();
}
/**
* return an array of the dwell times for all the steps.
*/
public long[] getSteps()
{
long[] steps = new long[stepValues.length];
for (int i=0; i<steps.length; i++) {
steps[i] = stepValues[i].getElapsedFrameTime();
}
return steps;
}
/**
* set the dwell time for all steps
*
* @param st dwell time in milliseconds
*
* @throws VisADException Couldn't create necessary VisAD object. The
* dwell time remains unchanged.
* @throws RemoteException Java RMI exception
*/
public void setStep(int st) throws VisADException, RemoteException {
if (st <= 0) {
throw new DisplayException("AnimationControlJ3D.setStep: " +
"step must be > 0");
}
step = st;
for (int i=0; i < stepValues.length; i++)
{
stepValues[i] = new WakeupOnElapsedTime(st);
}
// WLH 5 May 2000
// changeControl(true);
changeControl(false);
}
/**
* set the dwell time for individual steps.
*
* @param steps an array of dwell rates for each step in the animation
* If the length of the array is less than the number of
* frames in the animation, the subsequent step values will
* be set to the value of the last step.
*
* @throws VisADException Couldn't create necessary VisAD object. The
* dwell times remain unchanged.
* @throws RemoteException Java RMI exception
*/
public void setSteps(int[] steps)
throws VisADException, RemoteException {
// verify that the values are valid
for (int i = 0; i < stepValues.length; i++)
{
long step = (i < steps.length) ? steps[i] : steps[steps.length-1];
stepValues[i] = new WakeupOnElapsedTime(step);
if (step <= 0)
throw new DisplayException("AnimationControlJ3D.setSteps: " +
"step " + i + " must be > 0");
}
// WLH 5 May 2000
// changeControl(true);
changeControl(true);
}
/**
* advance one step (forward or backward)
*
* @throws VisADException Couldn't create necessary VisAD object. No
* step is taken.
* @throws RemoteException Java RMI exception
*/
public void takeStep() throws VisADException, RemoteException {
if (direction) current++;
else current--;
if (animationSet != null) {
current = animationSet.clipCurrent(current);
init();
}
getDisplayRenderer().render_trigger();
changeControl(false);
}
public void init() throws VisADException {
if (animationSet != null) {
double value = animationSet.getValue(current);
Set set = animationSet.getSet();
animation_string(real, set, value, current);
selectSwitches(value, set);
}
}
public Set getSet() {
if (animationSet != null) {
return animationSet.getSet();
}
else {
return null;
}
}
/**
* <p>Sets the set of times in this animation control. If the argument
* set is equal to the current set, then nothing is done.</p>
*
* @param s The set of times.
* @throws VisADException if a VisAD failure occurs.
* @throws RemoteException if a Java RMI failure occurs.
*/
public void setSet(Set s)
throws VisADException, RemoteException {
if (s == null && animationSet != null &&
animationSet.getSet() == null) return; // check for null/null
if (animationSet == null || s == null ||
(s != null && !s.equals(animationSet.getSet()))) {
setSet(s, false);
// have to do this if animationSet == null
if (s == null) {
stepValues = new WakeupOnElapsedTime[]{new WakeupOnElapsedTime(DEFAULT_DWELL)};
current = 0;
} else if (s.getLength() != stepValues.length) {
stepValues = new WakeupOnElapsedTime[s.getLength()];
for (int i = 0; i < stepValues.length; i++)
{
stepValues[i] = new WakeupOnElapsedTime(step);
}
}
}
}
/**
* <p>Sets the set of times in this animation control. If the argument
* set is equal to the current set, then nothing is done.</p>
*
* @param s The set of times.
* @param noChange changeControl(!noChange) to not trigger
* re-transform, used by ScalarMap.setRange
* @throws VisADException if a VisAD failure occurs.
* @throws RemoteException if a Java RMI failure occurs.
*/
public void setSet(Set s, boolean noChange)
throws VisADException, RemoteException {
if (animationSet != null) {
if (s == null) {
stepValues = new WakeupOnElapsedTime[]{new WakeupOnElapsedTime(DEFAULT_DWELL)};
current = 0;
} else if (s.getLength() != stepValues.length) {
stepValues = new WakeupOnElapsedTime[s.getLength()];
for (int i = 0; i < stepValues.length; i++)
{
stepValues[i] = new WakeupOnElapsedTime(step);
}
}
animationSet.setSet(s, noChange);
}
}
/** return true if automatic stepping is on */
public boolean getOn() {
if (animate != null) {
return animate.getOn();
}
else {
return false;
}
}
/**
* Set automatic stepping on or off.
*
* @param o true = turn stepping on, false = turn stepping off
*
* @throws VisADException Couldn't create necessary VisAD object. No
* change in automatic stepping occurs.
* @throws RemoteException Java RMI exception
*/
public void setOn(boolean o)
throws VisADException, RemoteException {
if (animate != null) {
animate.setOn(o);
}
}
/**
* toggle automatic stepping between off and on
*
* @throws VisADException Couldn't create necessary VisAD object. No
* change in automatic stepping occurs.
* @throws RemoteException Java RMI exception
*/
public void toggle()
throws VisADException, RemoteException {
if (animate != null) {
animate.setOn(!animate.getOn());
}
}
public RealType getRealType() {
return real;
}
public void subSetTicks() {
if (animationSet != null) {
animationSet.setTicks();
}
if (animate != null) {
animate.setTicks();
}
}
public boolean subCheckTicks(DataRenderer r, DataDisplayLink link) {
boolean flag = false;
if (animationSet != null) {
flag |= animationSet.checkTicks(r, link);
}
if (animate != null) {
flag |= animate.checkTicks(r, link);
}
return flag;
}
public boolean subPeekTicks(DataRenderer r, DataDisplayLink link) {
boolean flag = false;
if (animationSet != null) {
flag |= animationSet.peekTicks(r, link);
}
if (animate != null) {
flag |= animate.peekTicks(r, link);
}
return flag;
}
public void subResetTicks() {
if (animationSet != null) {
animationSet.resetTicks();
}
if (animate != null) {
animate.resetTicks();
}
}
/** get a String that can be used to reconstruct this
AnimationControl later */
public String getSaveString() {
int numSteps;
long[] steps;
if (stepValues == null) {
numSteps = 1;
steps = new long[1];
steps[0] = DEFAULT_DWELL;
}
else {
numSteps = stepValues.length;
steps = new long[numSteps];
for (int i = 0; i < numSteps; i++)
steps[i] = stepValues[i].getElapsedFrameTime();
}
StringBuffer sb = new StringBuffer(35 + 12 * numSteps);
sb.append(animate != null && animate.getOn());
sb.append(' ');
sb.append(direction);
sb.append(' ');
sb.append(current);
sb.append(' ');
sb.append(numSteps);
for (int i=0; i<numSteps; i++) {
sb.append(' ');
sb.append((int) steps[i]);
}
sb.append(' ');
sb.append(computeSet);
return sb.toString();
}
/** reconstruct this AnimationControl using the specified save string */
public void setSaveString(String save)
throws VisADException, RemoteException
{
if (save == null) throw new VisADException("Invalid save string");
StringTokenizer st = new StringTokenizer(save);
int numTokens = st.countTokens();
if (numTokens < 4) throw new VisADException("Invalid save string");
// get animation settings
boolean on = Convert.getBoolean(st.nextToken());
boolean dir = Convert.getBoolean(st.nextToken());
int cur = Convert.getInt(st.nextToken());
int numSteps = Convert.getInt(st.nextToken());
if (numSteps <= 0) {
throw new VisADException("Number of steps is not positive");
}
if (numTokens < 4 + numSteps) {
throw new VisADException("Not enough step entries");
}
int[] steps = new int[numSteps];
for (int i=0; i<numSteps; i++) {
steps[i] = Convert.getInt(st.nextToken());
if (steps[i] <= 0) {
throw new VisADException("Step #" + (i + 1) + "is not positive");
}
}
boolean cs = st.hasMoreTokens() ? Convert.getBoolean(st.nextToken()) : getComputeSet();
// set values
setOn(on);
setDirection(dir);
setSteps(steps);
setCurrent(cur);
setComputeSet(cs);
}
/** copy the state of a remote control to this control */
public void syncControl(Control rmt)
throws VisADException
{
if (rmt == null) {
throw new VisADException("Cannot synchronize " + getClass().getName() +
" with null Control object");
}
if (!(rmt instanceof AnimationControlJ3D)) {
throw new VisADException("Cannot synchronize " + getClass().getName() +
" with " + rmt.getClass().getName());
}
AnimationControlJ3D ac = (AnimationControlJ3D )rmt;
boolean changed = false;
/* *** DON'T TRY TO SYNC CURRENT FRAME!!! *** */
// if (current != ac.current) {
// changed = true;
// if (animationSet != null) {
// current = animationSet.getIndex(ac.current);
// canvas.renderTrigger();
// } else {
// current = 0;
// }
// }
if (direction != ac.direction) {
changed = true;
direction = ac.direction;
}
if (step != ac.step) {
changed = true;
step = ac.step;
}
if (animate != ac.animate) {
changed = true;
animate = ac.animate;
}
if (real != ac.real) {
changed = true;
real = ac.real;
}
if (computeSet != ac.computeSet) {
changed = true;
computeSet = ac.computeSet;
}
if (changed) {
try {
// WLH 5 May 2000
// changeControl(true);
changeControl(false);
} catch (RemoteException re) {
throw new VisADException("Could not indicate that control" +
" changed: " + re.getMessage());
}
}
}
public boolean equals(Object o)
{
if (!super.equals(o)) {
return false;
}
AnimationControlJ3D ac = (AnimationControlJ3D )o;
/**** IGNORE FRAME POSITION ****/
// if (current != ac.current) {
// return false;
// }
if (direction != ac.direction) {
return false;
}
if (step != ac.step) {
return false;
}
if (animate != ac.animate) {
return false;
}
if (real != ac.real) {
return false;
}
if (computeSet != ac.computeSet) {
return false;
}
return true;
}
/**
* Set the flag to automatically compute the animation set if it is
* null
* @param compute false to allow application to control set computation
* if set is null.
*/
public void setComputeSet(boolean compute) {
computeSet = compute;
}
/**
* Get the flag to automatically compute the animation set if it is
* null
*
* @return true if should compute set automatically when null
*/
public boolean getComputeSet() {
return computeSet;
}
/**
* Java3D <code>Behavior</code> which initiates animation stepping based on
* elapsed time.
*/
private class TakeStepBehavior extends Behavior {
public TakeStepBehavior() {
setSchedulingBounds(new BoundingSphere(new Point3d(0,0,0), 10000000));
}
// initialize dwell when added to universe
public void initialize() {
updateDwell();
}
/**
* Unconditionally attempt to take the next step.
*/
public void processStimulus(Enumeration criteria) {
try {
if (animate != null && animate.getOn()) {
takeStep();
}
} catch (VisADException v) {
throw new VisADError("Unable to take animation step", v);
} catch (RemoteException v) {
throw new VisADError("Unable to take animation step", v);
}
updateDwell();
}
/**
* Set the wake up criteria for the current step value (dwell).
*/
public void updateDwell() {
// set the wake up criteria with the dwell for the current step
if (0 <= current && current < stepValues.length) {
wakeupOn(stepValues[current]);
} else {
wakeupOn(new WakeupOnElapsedTime(DEFAULT_DWELL));
}
}
}
/**
* Stop animating.
*/
@Override
public void stop() {
steppingBehaviour.setEnable(false);
}
/**
* Start animating.
*/
@Override
public void run() {
steppingBehaviour.setEnable(true);
}
}