/* This file is part of the source code for 3D-XplorMath-J, Version 1.0 (January 2008). * Copyright (c) 2008 The 3D-XplorMath Consortium (http://3d-xplormath.org). * This source code is released under a BSD License, which allows redistribution * in source and binary form, with or without modification, provided copyright * and license information are included, and with no warranty or guarantee of * any kind. For details, see http://3d-xplormath.org/j/source/BSDLicense.txt */ package vmm.ode; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Font; import java.awt.Graphics2D; import java.awt.GridLayout; import java.awt.Rectangle; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.geom.Point2D; import java.io.IOException; import java.util.ArrayList; import javax.swing.BorderFactory; import javax.swing.Box; import javax.swing.JButton; import javax.swing.JCheckBox; import javax.swing.JLabel; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JRadioButton; import javax.swing.JTextField; import javax.swing.event.ChangeEvent; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.NodeList; import vmm.actions.AbstractActionVMM; import vmm.actions.ActionList; import vmm.actions.ActionRadioGroup; import vmm.actions.ToggleAction; import vmm.core.Decoration; import vmm.core.Display; import vmm.core.Exhibit; import vmm.core.I18n; import vmm.core.MouseTask; import vmm.core.SaveAndRestore; import vmm.core.TimerAnimation; import vmm.core.Transform; import vmm.core.Util; import vmm.core.VMMSave; import vmm.core.View; import vmm.core3D.BasicMouseTask3D; import vmm.core3D.Exhibit3D; import vmm.core3D.Vector3D; import vmm.core3D.View3D; /** * Represents an ODE exhibit that will be displayed in three dimensions. */ abstract public class ODE_3D extends Exhibit3D { public final static int ORBIT_TYPE_RUNGE_KUTTA = 0, ORBIT_TYPE_BOTH = 1, ORBIT_TYPE_EULER = 2; // (actual values are important) private final static int DF_SPACING = 30; // number of pixels between points in direction field private final static Color DIRECTION_FIELD_COLOR = Color.GRAY; private final static Color RUNGE_KUTTA_ORBIT_COLOR = Color.RED; private final static Color EULER_ORBIT_COLOR = Color.GREEN; private final static Color OLD_RUNGE_KUTTA_ORBIT_COLOR = new Color(150,80,80); private final static Color OLD_EULER_ORBIT_COLOR = new Color(80,150,80); private final static Color RUNGE_KUTTA_PROJECTED_ORBIT_X_COLOR = Color.CYAN; private final static Color RUNGE_KUTTA_PROJECTED_ORBIT_Y_COLOR = Color.MAGENTA; private final static Color RUNGE_KUTTA_PROJECTED_ORBIT_Z_COLOR = new Color(220,220,0); private final static Color EULER_PROJECTED_ORBIT_X_COLOR = new Color(100,150,150); private final static Color EULER_PROJECTED_ORBIT_Y_COLOR = new Color(150,100,150); private final static Color EULER_PROJECTED_ORBIT_Z_COLOR = new Color(150,150,80); /** * Tells whether the ODE is autonomous (i.e. independent of time). This final * variable is set by the constructor {@link #ODE_3D(boolean, boolean, String[])}, with the actual value * coming from the constructor of each subclass. Autonomous and non-autonomous * vector fields have different behaviors. */ protected final boolean isAutonomous; /** * If this is set to true in a subclass's constructor, then the view will be * set to show axes when it is created. The default is for the axes to be hidden. */ protected boolean showAxes = false; /** * If this is set to true in a subclass's constructor, then the view will be * put in anaglyph mode when it is created. The default is a monocular view. */ protected boolean anaglyphIsDefault = false; /** * If set to true in the constructor of a subclass, then the radio buttons * for selecting RungeKutta/Euler/Both will be added to the control * pandl. (Even when not added to the control panel, they will still * be in the Action menu). */ protected boolean addOrbitTypesToControlPanel = false; /** * If set to false in the constructor of a subclass, then the check box * for turning animatino of orbit drawing will not be added to the control * pandl (although the command will still be in the Action menu). */ protected boolean addAnimateCheckBoxontrolPanel = true; /** * If set to false in the constructor of a subclass, then the check box * for controling whether orbits are drawn as solid lines will not be added to the control * pandl (although the command will still be in the Action menu). */ protected boolean addLinesCheckBoxontrolPanel = true; /** * Tells whether it is possible to draw a vector field for the exhibit. This is * set in the contructor {@link #ODE_3D(boolean, boolean, String[])}, with the actual value coming * from the contructor of each subclass. If the value is false, then the * methods {@link #vectorField_x(double, double, double, double)} and * {@link #vectorField_y(double, double, double,double)} are never called. If the * value is true, then these methods must be overridden to return the correct * values. For an autonomous ODE, the display of the vector field * is under control of the user. For a non-autonomous ODE, the vector field * is shown only when an integral curve is being constructed. (Actually, * a direction field rather than a vector field is drawn.) */ protected final boolean canShowVectorField; /** * The names used for the input boxes where the user can type in the * initial values for an integeral curves. This array is set from the * String parameters to the constructor {@link #ODE_3D(boolean, boolean, String[])}. There * must be exactly as many strings as there are data items in the * pointData array that is passed to {@link #nextEulerPoint(double[], double)}, * {@link #nextRungeKuttaPoint(double[], double)} and {@link #extractPointFromData(double[])}. */ protected final String[] inputLabelNames; /** * Computes the next point on an integral curve, using Euler's method. * The pointData parameter array contains the data for the current point. * This data should be replaced with the data for the next point. The * size of the array and the meaning of the data that it contains will * vary from one subclass to another. However, for non-autonomous ODEs, * the time should always be the first parameter in the array. * The second parameter, dt, gives the time step from the current * point to the next point. */ abstract protected void nextEulerPoint(double[] pointData, double dt); /** * Computes the next point on an integral curve, using the Runge-Kutta method. * The pointData parameter array contains the data for the current point. * This data should be replaced with the data for the next point. The * size of the array and the meaning of the data that it contains will * vary from one subclass to another. But for the non-autonmous case, * the time should always be the first value in the array. * The second parameter, dt, gives the time step from the current point to the next point. */ abstract protected void nextRungeKuttaPoint(double[] pointData, double dt); /** * Extract from the data for a point on the integral curve the actual * point that is to be displayed on the screen. The default implementation * in this class takes the point from the first three components of the * array for an autonomous ODE and from the second, third, and fourth components * for a non-autonomous ODE, allowing the first spot for the current time. * Note that for non-autonomous ODEs, the time must be in the * first spot in the array. */ protected Vector3D extractPointFromData(double[] pointData) { if (isAutonomous) return new Vector3D(pointData[0],pointData[1],pointData[2]); else return new Vector3D(pointData[1],pointData[2],pointData[3]); } /** * Returns the x-component of the vector field at point (x,y,z) and time t. * For autonomous ODEs, the result should be independent of t. The * default implementation here simply returns 0. An exhibit that * sets the {@link #canShowVectorField} propery to true should * override this method to compute the correct value. */ protected double vectorField_x(double x, double y, double z, double t) { return 0; } /** * Returns the x-component of the vector field at point (x,y,z) and time t. * For autonomous ODEs, the result should be independent of t. The * default implementation here simply returns 0. An exhibit that * sets the {@link #canShowVectorField} propery to true should * override this method to compute the correct value. */ protected double vectorField_y(double x, double y, double z, double t) { return 0; } /** * Should construct and return a mouse task appropriate for this exhibit, to be used * in the specified view. The default return value is a standard * BasicMouseTask3D; subclasses can redefine this to add the ability to input * the initial conditions with the mouse. */ protected MouseTask makeDefaultMouseTask(ODEView view) { return new BasicMouseTask3D(); } /** * The value of this variable is used as the initial value of dt in the Control Panel * of any view of this exhbit. The default is 0.05, but it can be reset in the constructor * of a subclass. */ protected double dtDefault = 0.05; /** * The value of this variable is used as the initial value of Time Span in the Control Panel * of any view of this exhbit. The default is 10.0, but it can be reset in the constructor * of a subclass. */ protected double timeSpanDefault = 10; /** * Contains the initial conditions that are placed in the control panel when the * exhibit is first created. The value in this class is null, which means that * the initial condition input boxed in the control panel will be empty when it * first appears. Subclasses can set a different value. The value should be * an array of doubles, where the first several items have the same meaning as * the parameter in such methods as {@link #nextEulerPoint(double[], double)} and * {@link #extractPointFromData(double[])}. In addition there can be up to * two additional items. The first additional item, if present and greater than * zero, specifies a dt for the initial orbit. The second additional item, if * present and greater than zero, specifies a timeSpan for the initial orbit. * If these values are not present or are less than or equal to zero, then * the default dt/TimeSpan of the exhbit is used. */ protected double[] initialDataDefault = null; /** * Constructor sets the default background to be black, and sets the * value of the final "isAutonomous" property and the lables to be used * for the input boxes for initial conditions. For a non-autonomous ODE, * the first lable should be the label for the initial time input, generally * just "t". Also sets the default viewpoint to 10, 10, 5 */ protected ODE_3D(boolean canShowVectorField, boolean isAutonomous, String... inputLabelName) { setDefaultBackground(Color.BLACK); setDefaultViewpoint(new Vector3D(10,10,5)); this.canShowVectorField = canShowVectorField; this.isAutonomous = isAutonomous; this.inputLabelNames = inputLabelName; } /** * For an ODE view, returns the animation that redraws the current orbit, if there is one, * or draws the default orbit as defined by {@link #initialDataDefault}, if the value of * that variable is non-null. Otherwise, the return value is null. */ public TimerAnimation getCreateAnimation(View view) { if (view instanceof ODEView) return ((ODEView)view).makeCreateAnimation(); else return null; } /** * Transform a point on the screen to a point in 3D space. (The inverse * transformation that is used here to get from 3D viewing coords to * 3D object coords is valid only for a point that lies in the plane of * the screen (or for an orthographic projection). *
For cross-eye and stereograph view, where (x,y) might not lie in
* one of the view rects, the return value is null when (x,y) is outside
* the view rects.
*/
protected Vector3D screenPointTo3DPoint(View3D view, int x, int y) {
if (view.getViewStyle() == View3D.CROSS_EYE_STEREO_VIEW || view.getViewStyle() == View3D.STEREOGRAPH_VIEW) {
Rectangle r1 = view.stereographLeftEyeRect();
Rectangle r2 = view.stereographRightEyeRect();
if (r1.contains(x, y)) {
x = x - r1.x;
y = y - r1.y;
}
else if (r2.contains(x,y)) {
x = x - r2.x;
y = y - r2.y;
}
else
return null;
}
Point2D p = new Point2D.Double(x,y);
view.getTransform3D().viewportToWindow(p);
Vector3D v = new Vector3D(p.getX(), 0, p.getY());
Vector3D xDir = view.getTransform3D().getImagePlaneXDirection();
Vector3D zDir = view.getTransform3D().getImagePlaneYDirection();
Vector3D yDir = view.getTransform3D().getViewDirection().negated();
Vector3D v2 = new Vector3D();
v2.x = v.x * xDir.x + v.y * yDir.x + v.z * zDir.x;
v2.y = v.x * xDir.y + v.y * yDir.y + v.z * zDir.y;
v2.z = v.x * xDir.z + v.y * yDir.z + v.z * zDir.z;
return v2;
}
/**
* Draws a direction field, but only for an exhbit in which the {@link #canShowVectorField}
* property is true and then only if the exhibit is being displayed in an ODEView whose
* showDirectionField property is turned on (or in the unlikely event that it is being
* displayed in some other type of View).
* @see ODEView#setShowDirectionField(boolean)
*/
protected void doDraw(Graphics2D g, View view, Transform transform) {
if ( ! canShowVectorField )
return;
if ( !(view instanceof ODEView) || ((ODEView)view).showDirectionField)
drawDirectionField(view,transform);
}
private void drawDirectionField(View view, Transform transform) { // draws the direction field
// double spacing = DF_SPACING * transform.getPixelWidth(); // spacing between points, in terms of xy-coords, not pixels.
// int startX, endX, startY, endY; // limits for the for loops, computed so that the direction field fills the view
// startX = (int)( transform.getXmin()/spacing ) - 1;
// endX = (int)( transform.getXmax()/spacing ) + 1;
// startY = (int)( transform.getYmin()/spacing ) - 1;
// endY = (int)( transform.getYmax()/spacing ) + 1;
// Color c = view.getColor();
// view.setColor(DIRECTION_FIELD_COLOR);
// double currentTime = 0;
// if (view instanceof ODEView)
// currentTime = ((ODEView)view).currentTime;
// for (int i = startX; i <= endX; i++) {
// double x = i*spacing;
// for (int j = startY; j <= endY; j++) {
// double y = j*spacing;
// double dx = vectorField_x(x, y, z, currentTime);
// double dy = vectorField_y(x, y, z, currentTime);
// double length = Math.sqrt(dx*dx + dy*dy);
// if (Double.isNaN(length)) {
// }
// else if (Math.abs(length) < 0.000001) {
// view.drawPixel(x,y);
// }
// else {
// dx = dx/length * (spacing/4);
// dy = dy/length * (spacing/4);
// view.drawLine(x-dx,y-dy,x+dx,y+dy);
// }
// }
// }
// view.setColor(c);
}
/**
* Returns a view of type {@link ODEView}.
*/
public View getDefaultView() {
return new ODEView();
}
/**
* Represents the default view for an ODEFirstOrder2D.
*/
public class ODEView extends View3D {
private boolean showDirectionField; // (Saved, by hand, for the autonomous case only).
@VMMSave private boolean showProjectedOrbits = false;
@VMMSave private boolean showControlPanel = true;
@VMMSave private int orbitType = ORBIT_TYPE_RUNGE_KUTTA;
@VMMSave private boolean connectDotsOnOrbit; // Should orbits be drawn as dots or as solid lines?
private boolean animateDrawing = true;
private Orbit currentOrbit; // The most recently added orbit, shown in green; some commands operate on this orbit.
private ControlPanel controlPanel;
private ProjectedOrbitView projectedOrbitView;
private double[] initialDataForCreateAnimation;
private double currentTime; // used only for non-autonomous ODES, while drawing an orbit;
protected ToggleAction showDirectionFieldToggle;
protected ToggleAction showProjectedOrbitsToggle = new ToggleAction(I18n.tr("vmm.ode.command.ShowProjectedOrbits"),false) {
public void actionPerformed(ActionEvent evt) {
setShowProjectedOrbits(getState());
}
};
protected ToggleAction showControlPanelToggle = new ToggleAction(I18n.tr("vmm.ode.command.ShowControlPanel"),true) {
public void actionPerformed(ActionEvent evt) {
setShowControlPanel(getState());
}
};
protected ToggleAction animateDrawingToggle = new ToggleAction(I18n.tr("vmm.ode.command.AnimateDrawing"),true) {
public void actionPerformed(ActionEvent evt) {
setAnimateDrawing(getState());
}
};
protected AbstractActionVMM continueOrbitAction = new AbstractActionVMM(I18n.tr("vmm.ode.command.ContinueOrbit")) {
public void actionPerformed(ActionEvent evt) {
Orbit orbit = getCurrentOrbit();
if (orbit != null) {
double timeSpan;
try {
timeSpan = Double.parseDouble(controlPanel.timeSpanInput.getText());
}
catch (Exception e) {
timeSpan = timeSpanDefault;
controlPanel.timeSpanInput.setText(""+timeSpanDefault);
}
int numberOfPoints = (int)(timeSpan/orbit.dt + 0.5);
controlPanel.dtInput.setText(""+orbit.dt);
if (animateDrawing)
getDisplay().installAnimation(new ExtendOrbitAnimation(ODEView.this,orbit,numberOfPoints));
else
orbit.setPointCount(orbit.getPointCount() + numberOfPoints);
}
}
};
protected ToggleAction connectDotsToggle = new ToggleAction(I18n.tr("vmm.ode.command.ConnectDotsOnOrbit"),false) {
public void actionPerformed(ActionEvent evt) {
setConnectDotsOnOrbit(getState());
}
};
protected AbstractActionVMM eraseOrbitsAction = new AbstractActionVMM(I18n.tr("vmm.ode.command.EraseOrbits")) {
public void actionPerformed(ActionEvent evt) {
if (getDisplay() != null)
getDisplay().stopAnimation();
setCurrentOrbit(null);
Decoration[] decorations = getDecorations();
for (Decoration dec : decorations)
if (dec instanceof Orbit)
removeDecoration(dec);
}
};
protected ActionRadioGroup orbitTypeSelect = new ActionRadioGroup( new String[] {
I18n.tr("vmm.ode.command.orbitType.RungeKutta"),
I18n.tr("vmm.ode.command.orbitType.Euler"),
I18n.tr("vmm.ode.command.orbitType.Both"),
}, 0) {
public void optionSelected(int option) {
int style;
if (option == 0)
style = ORBIT_TYPE_RUNGE_KUTTA;
else if (option == 1)
style = ORBIT_TYPE_EULER;
else
style = ORBIT_TYPE_BOTH;
setOrbitType(style);
}
};
public ODEView() {
if (showAxes)
setShowAxes(true);
if (anaglyphIsDefault)
setViewStyle(View3D.RED_GREEN_STEREO_VIEW);
setAntialiased(true);
continueOrbitAction.setEnabled( getCurrentOrbit() != null );
controlPanel = new ControlPanel(this);
showDirectionField = isAutonomous && canShowVectorField;
if (isAutonomous && canShowVectorField)
showDirectionFieldToggle = new ToggleAction(I18n.tr("vmm.ode.command.ShowDirectionField"),true) {
public void actionPerformed(ActionEvent evt) {
setShowDirectionField(getState());
}
};
}
public boolean getShowDirectionField() {
return showDirectionField;
}
/**
* The showDirectionField property determines whether a direction field is drawn for the exhibit in this view.
* This property is true by default. This method has no effect if the exhbit can't show direction fields.
*/
public void setShowDirectionField(boolean showDirectionField) {
if (!canShowVectorField)
return;
if (this.showDirectionField == showDirectionField)
return;
this.showDirectionField = showDirectionField;
if (showDirectionFieldToggle != null)
showDirectionFieldToggle.setState(showDirectionField);
forceRedraw();
}
public boolean getAnimateDrawing() {
return animateDrawing;
}
public void setAnimateDrawing(boolean animateDrawing) {
this.animateDrawing = animateDrawing;
animateDrawingToggle.setState(animateDrawing);
}
/**
* Returns the current orbit (shown in green), if any.
*/
protected Orbit getCurrentOrbit() {
return currentOrbit;
}
/**
* Sets the current orbit (shown in green). When a new orbit is added to the exhibit,
* this method is called, so that the new orbit becomes the current orbit.
*/
protected void setCurrentOrbit(Orbit orbit) {
if (currentOrbit == orbit)
return;
if (currentOrbit != null)
currentOrbit.setIsCurrentOrbit(false);
currentOrbit = orbit;
if (currentOrbit != null)
currentOrbit.setIsCurrentOrbit(true);
continueOrbitAction.setEnabled( currentOrbit != null);
}
/**
* Tells whether orbits should be drawn as dots or as solid lines.
* @see #setConnectDotsOnOrbit(boolean)
*/
public boolean getConnectDotsOnOrbit() {
return connectDotsOnOrbit;
}
/**
* Set the property that tells whether an orbit should be drawn simply as a sequence of dots,
* or the dots should be connected to make a solid curve. If there is a current (green) orbit
* when the property is changed, the new value is applied immediately to the current orbit.
* It also applies to new orbits created in the future. It does not change the appearance
* of old (red) orbits that exist when the value of the property is set.
*/
public void setConnectDotsOnOrbit(boolean connectDotsOnOrbit) {
if (this.connectDotsOnOrbit == connectDotsOnOrbit)
return;
this.connectDotsOnOrbit = connectDotsOnOrbit;
connectDotsToggle.setState(connectDotsOnOrbit);
if (currentOrbit != null)
currentOrbit.setStyle(connectDotsOnOrbit ? OrbitPoints2D.LINES : OrbitPoints2D.DOTS);
}
public boolean getShowProjectedOrbits() {
return showProjectedOrbits;
}
public int getOrbitType() {
return orbitType;
}
public void setOrbitType(int type) {
if (type == orbitType)
return;
orbitType = type;
if (currentOrbit != null) {
currentOrbit.setOrbitType(orbitType);
if (showProjectedOrbits)
projectedOrbitView.forceRedraw();
}
}
public boolean getShowControlPanel() {
return showControlPanel;
}
public void setShowControlPanel(boolean showControlPanel) {
if (this.showControlPanel == showControlPanel)
return;
this.showControlPanel = showControlPanel;
showControlPanelToggle.setState(showControlPanel);
if (showControlPanel) {
if (getDisplay() != null) {
getDisplay().getHolder().add(controlPanel,BorderLayout.EAST);
getDisplay().getHolder().validate();
}
}
else {
if (getDisplay() != null) {
getDisplay().getHolder().remove(controlPanel);
getDisplay().getHolder().validate();
}
}
}
/**
* When the showProjectedOrbits property is true, an auxiliary view is added to the bottom of the
* display where the x- and y-coordinates of the points on the current orbit are plotted.
* This property is false by default.
*/
public void setShowProjectedOrbits(boolean showProjectedOrbits) {
if (this.showProjectedOrbits == showProjectedOrbits)
return;
this.showProjectedOrbits = showProjectedOrbits;
showProjectedOrbitsToggle.setState(showProjectedOrbits);
if (showProjectedOrbits) {
projectedOrbitView = new ProjectedOrbitView(this);
if (getCurrentOrbit() != null) {
projectedOrbitView.setMaxPoints(getCurrentOrbit().getPointCount());
projectedOrbitView.resetPointsFromOrbit(getCurrentOrbit());
}
if (getDisplay() != null)
getDisplay().installAuxiliaryView(this, projectedOrbitView, Display.AUX_VIEW_ON_BOTTOM, 0.2, true);
}
else {
projectedOrbitView = null;
if (getDisplay() != null)
getDisplay().installAuxiliaryView(this, null);
}
}
public ActionList getActions() {
ActionList actions = super.getActions();
actions.add(continueOrbitAction);
actions.add(eraseOrbitsAction);
actions.add(connectDotsToggle);
actions.add(animateDrawingToggle);
actions.add(null);
actions.add(orbitTypeSelect);
actions.add(null);
if (showDirectionFieldToggle != null)
actions.add(showDirectionFieldToggle);
actions.add(showControlPanelToggle);
actions.add(showProjectedOrbitsToggle);
return actions;
}
public void setDisplay(Display display) {
super.setDisplay(display);
if (display != null)
display.setStopAnimationsOnResize(false);
if (display != null && projectedOrbitView != null)
display.installAuxiliaryView(this, projectedOrbitView, Display.AUX_VIEW_ON_BOTTOM, 0.2, true);
if (display != null && showControlPanel) {
display.getHolder().add(controlPanel,BorderLayout.EAST);
display.getHolder().validate();
}
}
public void setExhibit(Exhibit ex) {
super.setExhibit(ex);
if (ex != null && ex == ODE_3D.this) // It better equal ODE_3D.this!
initialDataForCreateAnimation = initialDataDefault;
}
public void stateChanged(ChangeEvent evt) {
super.stateChanged(evt);
Object source = evt.getSource();
if (projectedOrbitView != null && source instanceof Transform)
projectedOrbitView.forceRedraw();
}
public MouseTask getDefaultMouseTask() {
return makeDefaultMouseTask(this);
}
public double getCurrentTimeFromControlPanel() { // for use by mouse tasks to construct initialPointData
if (isAutonomous)
return 0;
try {
return Double.parseDouble(controlPanel.icInputs[0].getText());
}
catch (NumberFormatException e) {
controlPanel.icInputs[0].setText("0");
return 0;
}
}
public void startOrbitAtPoint(double[] initialPointData) {
double dt, timeSpan;
try {
dt = Double.parseDouble(controlPanel.dtInput.getText());
if (dt <= 0)
throw new Exception();
}
catch (Exception e) {
dt = dtDefault;
controlPanel.dtInput.setText(""+dtDefault);
}
try {
timeSpan = Double.parseDouble(controlPanel.timeSpanInput.getText());
if (timeSpan <= 0)
throw new Exception();
}
catch (Exception e) {
timeSpan = timeSpanDefault;
controlPanel.timeSpanInput.setText(""+timeSpanDefault);
}
controlPanel.resetStartPointInputText(initialPointData);
startOrbitAtPoint(initialPointData, dt, timeSpan);
}
private TimerAnimation makeCreateAnimation() {
if (!animateDrawing)
return null;
if (currentOrbit == null && initialDataForCreateAnimation == null)
return null;
Orbit orbit;
double dt;
double timeSpan;
if (currentOrbit != null) {
orbit = currentOrbit;
dt = currentOrbit.dt;
timeSpan = dt * currentOrbit.pointCount;
currentOrbit.removePoints();
}
else {
int dataCt = inputLabelNames.length; // number of intial condition values in initialDataDefault
if (initialDataForCreateAnimation.length > dataCt && initialDataForCreateAnimation[dataCt] > 0)
dt = initialDataForCreateAnimation[dataCt];
else
dt = dtDefault;
if (initialDataForCreateAnimation.length > dataCt + 1 && initialDataForCreateAnimation[dataCt+1] > 0)
timeSpan = initialDataForCreateAnimation[dataCt+1];
else
timeSpan = timeSpanDefault;
double[] data = new double[dataCt];
for (int i = 0; i < dataCt; i++)
data[i] = initialDataForCreateAnimation[i];
orbit = new Orbit(this,data,ORBIT_TYPE_RUNGE_KUTTA,dt);
controlPanel.resetStartPointInputText(initialDataForCreateAnimation);
addDecoration(orbit);
setCurrentOrbit(orbit);
}
initialDataForCreateAnimation = null;
int numberOfPoints = (int)(timeSpan/dt) + 1; // how many points should be added to curve during animation
controlPanel.dtInput.setText(""+dt);
controlPanel.timeSpanInput.setText(""+timeSpan);
if (!isAutonomous)
currentTime = orbit.initialData[0];
return new ExtendOrbitAnimation(this,orbit,numberOfPoints);
}
private void startOrbitAtPoint(double[] initialPointData, double dt, double timeSpan) {
Orbit orbit;
orbit = new Orbit(this,initialPointData,orbitType,dt);
addDecoration(orbit);
setCurrentOrbit(orbit);
if (getConnectDotsOnOrbit())
orbit.setStyle(OrbitPoints2D.LINES);
int numberOfPoints = (int)(timeSpan/dt + 0.5); // how many points should be added to curve during animation
if (!isAutonomous && animateDrawing)
currentTime = initialPointData[0];
if (animateDrawing)
getDisplay().installAnimation( new ExtendOrbitAnimation(this,orbit,numberOfPoints));
else
orbit.setPointCount(numberOfPoints+1);
}
public void addExtraXML(Document containingDocument, Element viewElement) {
super.addExtraXML(containingDocument, viewElement);
if (isAutonomous && canShowVectorField)
SaveAndRestore.addProperty(this, "showDirectionField", containingDocument, viewElement);
for (Decoration d : getDecorations())
if (d instanceof Orbit) {
Orbit orbit = (Orbit)d;
Element orbElm = containingDocument.createElement("orbit");
orbElm.setAttribute("start", Util.toExternalString(orbit.initialData));
orbElm.setAttribute("type", Util.toExternalString(orbit.orbitType));
orbElm.setAttribute("dt", Util.toExternalString(orbit.dt));
orbElm.setAttribute("points", Util.toExternalString(orbit.pointCount));
orbElm.setAttribute("isCurrentOrbit", Util.toExternalString(orbit.isCurrentOrbit));
orbElm.setAttribute("style", "" + orbit.eulerPoints.getStyle());
viewElement.appendChild(orbElm);
}
}
public void readExtraXML(Element viewInfo) throws IOException {
super.readExtraXML(viewInfo);
NodeList nodes = viewInfo.getElementsByTagName("orbit");
for (int i = 0; i < nodes.getLength(); i++) {
Element orbElm = (Element)nodes.item(i);
double[] startPt = (double[])Util.externalStringToValue(orbElm.getAttribute("start"), double[].class);
double dt = (Double)Util.externalStringToValue(orbElm.getAttribute("dt"), Double.TYPE);
int type = (Integer)Util.externalStringToValue(orbElm.getAttribute("type"), Integer.TYPE);
int pointCount = (Integer)Util.externalStringToValue(orbElm.getAttribute("points"), Integer.TYPE);
boolean isCurrentOrbit = (Boolean)Util.externalStringToValue(orbElm.getAttribute("isCurrentOrbit"), Boolean.TYPE);
int style = (Integer)Util.externalStringToValue(orbElm.getAttribute("style"), Integer.TYPE);
Orbit orbit = new Orbit(this,startPt,type,dt);
orbit.pointCount = pointCount;
orbit.setIsCurrentOrbit(isCurrentOrbit);
orbit.setStyle(style);
addDecoration(orbit);
if (isCurrentOrbit) {
controlPanel.dtInput.setText(""+dt);
controlPanel.resetStartPointInputText(startPt);
currentOrbit = orbit;
continueOrbitAction.setEnabled(true);
}
}
}
}
/**
* This class defines the auxiliary view that can be added to the bottom of the display,
* where the x- and y-coordinates of the current orbit are plotted. It is a rather kludgy hack.
*/
private class ProjectedOrbitView extends View {
ArrayList