/* 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 vmm3d.latticemodel;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Font;
import org.freehep.graphics2d.VectorGraphics;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import javax.swing.Action;
import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JRadioButton;
import javax.swing.JTextField;
import vmm3d.actions.AbstractActionVMM;
import vmm3d.actions.ActionList;
import vmm3d.actions.ActionRadioGroup;
import vmm3d.actions.ToggleAction;
import vmm3d.core.Animation;
import vmm3d.core.Display;
import vmm3d.core.I18n;
import vmm3d.core.IntegerParam;
import vmm3d.core.MouseTask;
import vmm3d.core.Parameter;
import vmm3d.core.ParameterDialog;
import vmm3d.core.ParameterInput;
import vmm3d.core.RealParam;
import vmm3d.core.TimerAnimation;
import vmm3d.core.Transform;
import vmm3d.core.Util;
import vmm3d.core.VMMSave;
import vmm3d.core.View;
import vmm3d.core3D.Exhibit3D;
import vmm3d.core3D.Transform3D;
import vmm3d.core3D.Vector3D;
import vmm3d.core3D.View3D;
import vmm3d.fourier.*;
import vmm3d.functions.ParseError;
abstract public class LatticeModel extends Exhibit3D {
public final static int MAX_NUMBER_OF_NODES = 256; // was originally 512---this caused problems
// values for view.getDisplayStyle()
public final static int DISPLAY_TRANSVERSE = 0;
public final static int DISPLAY_LONGITUDINAL = 1;
public final static int DISPLAY_CIRCILAR = 2;
public final static int DISPLAY_PENDULUM = 3;
public final static int DISPLAY_BRIDGE = 4;
public final static int DISPLAY_FPU_GRAPH = 5; // (only used in the FermiPastaUlam exhibit)
// values for view.getBoundaryCondition()
public final static int BOUNDARY_CONDITION_ZERO = 0;
public final static int BOUNDARY_CONDITION_PERIODIC = 1;
// values for view.getInitialShape();
public final static int INITIAL_SHAPE_SINUSOIDAL = 0;
public final static int INITIAL_SHAPE_GAUSSIAN = 1;
public final static int INITIAL_SHAPE_THERMAL = 2;
public final static int INITIAL_SHAPE_KINK = 3;
public final static int INITIAL_SHAPE_BREATHER = 4;
public final static int INITIAL_MODE_FIRST = 0; // values for view.getInitialMode()
public final static int INITIAL_MODE_SECOND = 1;
public final static int INITIAL_MODE_FOURTH = 2;
public final static int INITIAL_MODE_EIGHTH = 3;
public final static int INITIAL_MODE_SIXTEENTH = 4;
// customization variables that are meant to be set in the constructors of subclasses
protected int numIterations; // the number of iterations of Runge-Kutta per step.
protected double defaultStepSize = 0.1;
protected int defaultBoundaryCondition = BOUNDARY_CONDITION_ZERO;
protected int defaultInitialShape = INITIAL_SHAPE_SINUSOIDAL;
protected int defaultInitialMode = INITIAL_MODE_FIRST;
protected int defaultDisplayStyle = DISPLAY_TRANSVERSE;
protected boolean canShowNormalModeDisplay = true; //false;
protected double defaultLatticeLength = 6.2832;
protected double defaultLatticeDensity = 1.0;
protected double defaultAmplitude = 1.0;
protected int defaultNumberOfNodes = 32;
protected double defaultScaleFactor = 1;
protected boolean MaxPermittedDisplacementExceeded; // some node has moved too far from its equilibrium position. Should abort lattice evolution;
abstract protected double potentialEnergy(double x, double theLatticeSpacing); //gives the potential energy of a spring that joins the nodes. x is displacement from equilibrium
abstract protected double internalForceLaw(double x, double theLatticeSpacing); // gives the restoring force of a spring when it is stretched x units from equilibrium
abstract protected double externalForceLaw(double x, double theLatticeSpacing);
protected class LatticeData { // stores the state of the lattice model at a given time.
public double currentTime = 0;
public double stepNumber = 0;
int nodeCount;
public double stepSize; // This will be set equal to the step size from the control panel at each step.
public double density;
public double maxDisplacement; // The gg ofPascal version; maximum initial dosplacement.
public double youngsModulus;
public double latticeLength;
public int latticeBoundaryCondition;
public int latticeInitialShape;
public int latticeInitialMode;
public double TotalModalEnergy;
public double[] currentDisplacement = new double[2*MAX_NUMBER_OF_NODES+1]; // gives the current displacements of the lattice nodes from equilibrium
public double[] currentVelocity = new double[2*MAX_NUMBER_OF_NODES+1]; //gives the current velocities of the lattice nodes
public double[] RealNormalModeAngularFrequencies = new double[2*MAX_NUMBER_OF_NODES+1];
public double[] MyWaveArray = new double[2*MAX_NUMBER_OF_NODES+1]; // holds FT of currentDisplacement
public double[] MyVelocityArray = new double[2*MAX_NUMBER_OF_NODES+1]; // holds FT of currentVelocity
private void CopyCurrentDisplacementsAndCurrentVelocitiesToMyWaveArrayAndMyVelocityArray() {
for (int i = 1; i <= nodeCount; i++) {
MyWaveArray[i] = currentDisplacement[i];
MyVelocityArray[i] = currentVelocity[i];
}
}
private void GetRealNormalModeAngularFrequencies(LMView view) {
double gamma = Math.sqrt(youngsModulus/(massPerNode(view) * 16 *latticeSpacing(view)));
for (int mode = 1; mode <= nodeCount; mode++)
RealNormalModeAngularFrequencies[mode] = 2 * gamma * Math.sin( (mode * Math.PI)/(2 * nodeCount ) );
}
private void DSTMyWaveArrayAndMyVelocityArray() {
FourierSeries.DSTinPlace(MyWaveArray, nodeCount);
FourierSeries.DSTinPlace(MyVelocityArray,nodeCount);
}
private void IDSTMyWaveArrayAndMyVelocityArray() {
FourierSeries.IDSTinPlace(MyWaveArray, nodeCount);
FourierSeries.IDSTinPlace(MyVelocityArray,nodeCount);
}
double sqr(double x) {return x*x;}
private double PotentialEnergyOfMode (int k) {
return 17.77*Math.sqrt(2) * sqr( RealNormalModeAngularFrequencies[k]) * sqr( MyWaveArray[ k ] );
}
private double KineticEnergyOfMode (int k) {
return 17.77*Math.sqrt(2) * sqr(MyVelocityArray [ k ] );
}
private double TotalEnergyOfMode (int k) {
return KineticEnergyOfMode(k) + PotentialEnergyOfMode(k);
}
private void GetLatticeModeEnergies(LMView view) {
CopyCurrentDisplacementsAndCurrentVelocitiesToMyWaveArrayAndMyVelocityArray();
DSTMyWaveArrayAndMyVelocityArray();
GetRealNormalModeAngularFrequencies(view);
// for (int i = 1; i <= 2 * nodeCount; i++) {
// System.out.println(i);
// System.out.println(MyWaveArray[i]); }
double TotalEnergyOfMode_i;
TotalModalEnergy = 0.0;
for (int i = 1; i <= nodeCount; i++) {
TotalEnergyOfMode_i = KineticEnergyOfMode(i) + PotentialEnergyOfMode(i);
TotalModalEnergy = TotalModalEnergy + TotalEnergyOfMode_i;
// if ( (stepNumber % 100) == 1) {
// if ( ( ( (i % latticeInitialMode) == 0) & ( (i / latticeInitialMode) < 6)) & ((stepNumber / 100) < 10000) )
// MyFPUGraphData[(i/latticeInitialMode),(stepNumber/100)] = TotalEnergyOfMode_i; }
}
}
// These next methods define some auxilliary lattice data,
// latticeSpacing (the equilibrium distance between nodes)
// latticeAbscissa(i) the equilibrium position of the i-th node, and
// massPerNode = total mass (= latticeLength times density) divided by numberOfNodes
// as functions of the primary lattice variables, the
// latticeLength, the density, and the numberOfNodes.
public double latticeSpacing(LMView view){
return latticeLength /view.getNumberOfNodes();
}
private double LatticeAbscissa (int NodeIndex,LMView view) {
return NodeIndex * latticeSpacing(view);
}
public double massPerNode(LMView view) {
return density * latticeSpacing(view);
}
// These next routines give the initial "shapes" of the Lattice string, in terms of
// the displacement x of a nodes from its equilibrium position.
// The methods pulseWidth and half its reciprocal, waveNumber are convenience routines.
public double pulseWidth() {
return 1.0/Math.pow(2.0,(double)latticeInitialMode);
}
public double waveNumber() {
return 1.0/(2*pulseWidth());
}
private double SineWave (double x, double WaveNumber, double FundamentalLength) {
return maxDisplacement * Math.sin((2 * Math.PI * waveNumber() / FundamentalLength) * x);
}
private double Breather(double x,double u){ // u is speed
return 4 * Math.atan((Math.sqrt(1 - u * u) / u) * (1 / Math.cosh(Math.sqrt(1 - u * u) * x)));
}
private double Kink (double s,double u) { // u is speed
return 4 * Math.atan(Math.exp((s / Math.sqrt(1 - u * u))));
}
private double Gaussian (double x, double sigma) { //sigma is the standard deviation
return (1 / (sigma * Math.sqrt(2 * Math.PI))) * Math.exp(-((x*x) / (sigma*sigma)));
}
private double shapeFunction(double x,double sigma,double u) { //x is the LatticeAbscissa of a node
switch (latticeInitialShape) {
case INITIAL_SHAPE_SINUSOIDAL: return Math.sqrt(2) * SineWave(x, waveNumber(), latticeLength);
case INITIAL_SHAPE_GAUSSIAN: return Gaussian(x,sigma);
case INITIAL_SHAPE_THERMAL: return 0;
case INITIAL_SHAPE_KINK: return Kink(x,u);
case INITIAL_SHAPE_BREATHER: return Breather(x,u);
default: return 0;
}
}
private void InitializeDisplacementsAndVelocities(LMView view){
int i;
for( i = 1; i <= view.getNumberOfNodes(); i++) {
currentVelocity[i] = 0.0; // the string is "plucked" not struck
currentDisplacement[i] = shapeFunction(LatticeAbscissa(i,view),1,1);
currentVelocity[0] = 0.0;
currentDisplacement[0] = currentDisplacement[view.getNumberOfNodes()];
}
}
public LatticeData(LMView view) {
//This construtor should initialize the LatticeData, using the values of (at least) view.getNumberOfNodes(),
// view.getInitialShape(), view.getInitialMode(), view.getBoundaryCondition(), view.latticeLength.getValue(),
// and view.latticeDensity.getValue()
nodeCount = view.getNumberOfNodes();
density = view.latticeDensity.getValue();
maxDisplacement = view.amplitude.getValue();
youngsModulus = 1;
latticeLength = view.latticeLength.getValue();
latticeBoundaryCondition = view.getBoundaryCondition();
latticeInitialShape = view.getInitialShape();
latticeInitialMode = view.getInitialMode();
InitializeDisplacementsAndVelocities(view);
}
// The next few methods step the time forward by time step using Runge-Kutta to integrate Newton's Equations
// of Motion Force = mass * acceleration to get the acceleration from the force law that defines the particular
// lattice model. NextNodeIndex(i,view) is a utility routine returning i + 1) mod numberOfNodes
private int NextNodeIndex(int i, int numNodes){ // NextNodeIndex := (i + 1) mod NumberOfNodes
if ((i >= numNodes ) || (i < 0) ) return 0;
return (i + 1);
}
private double PotentialEnergyOfSpring(LMView view,int node) { //Node is the node to left of spring}
return potentialEnergy(currentDisplacement[NextNodeIndex(node,nodeCount)] - currentDisplacement[node],
latticeSpacing(view));
}
private double KineticEnergyOfNode(LMView view, int node) {
return 0.5 * massPerNode(view) * currentVelocity[node] * currentVelocity[node];
}
private double LatticeTotalKineticEnergy(LMView view) {
double KE= 0;
for (int node=1; node <= nodeCount; node++)
KE = KE + KineticEnergyOfNode(view, node);
return KE;
}
private double LatticeTotalPotentialEnergy(LMView view) {
double PE = 0;
for (int spring=0; spring <= nodeCount; spring++) //there are nodeCount + 1 springs!!
PE = PE + PotentialEnergyOfSpring(view,spring);
return PE;
}
protected double LatticeTotalEnergy(LMView view) {
return LatticeTotalPotentialEnergy(view) + LatticeTotalKineticEnergy(view);
}
// This is the Acceleration of theNode from the forces due to adjacent modes and any external force.
private double Acceleration (LMView view,int theNode, double PreviousNodeDisplacement,
double theNodeDisplacement, double NextNodeDisplacement) {
double externalForce, internalForce, totalForce;
if ( (latticeBoundaryCondition == BOUNDARY_CONDITION_ZERO) &
(( theNode == 0 ) || ( theNode == nodeCount ))) return 0;
internalForce = internalForceLaw(theNodeDisplacement - PreviousNodeDisplacement, latticeSpacing(view)) -
internalForceLaw(NextNodeDisplacement - theNodeDisplacement,latticeSpacing(view));
externalForce = externalForceLaw(theNodeDisplacement,latticeSpacing(view));
totalForce = internalForce + externalForce;
return totalForce/massPerNode(view); // Newton's Law: acceleration = Force/mass
}
protected void RungeKuttaLattice( double[] x0, double[] u0,LMView view){
double h = stepSize;
double[] h1 = new double[nodeCount + 1];
double[] k1 = new double[nodeCount + 1];
double[] h2 = new double[nodeCount + 1];
double[] k2 = new double[nodeCount + 1];
double[] h3 = new double[nodeCount + 1];
double[] k3 = new double[nodeCount + 1];
double[] h4 = new double[nodeCount + 1];
double[] k4 = new double[nodeCount + 1];
int i;
for( i = 1; i <= nodeCount; i++) {
h1[i] = h * u0[i];
k1[i] = h * Acceleration(view,i, x0[i - 1], x0[i], x0[NextNodeIndex(i,nodeCount)]);
}
h1[0] = h1[nodeCount];
k1[0] = k1[nodeCount];
for( i = 1; i <= nodeCount; i++) {
h2[i] = h * (u0[i] + k1[i] / 2);
k2[i] = h * Acceleration(view,i, x0[i - 1] + h1[i - 1] / 2, x0[i] + h1[i] / 2,
x0[NextNodeIndex(i,nodeCount)] + h1[NextNodeIndex(i,nodeCount)] / 2);
}
h2[0] = h2[nodeCount];
k2[0] = k2[nodeCount];
for( i = 1; i <= nodeCount; i++) {
h3[i] = h * (u0[i] + k2[i] / 2);
k3[i] = h * Acceleration(view,i, x0[i - 1] + h2[i - 1] / 2, x0[i] + h2[i] / 2,
x0[NextNodeIndex(i,nodeCount)] + h2[NextNodeIndex(i,nodeCount)] / 2);
}
h3[0] = h3[nodeCount];
k3[0] = k3[nodeCount];
for( i = 1; i <= nodeCount; i++) {
h4[i] = h * (u0[i] + k3[i]);
k4[i] = h * Acceleration(view,i, x0[i - 1] + h3[i - 1], x0[i] + h3[i],
x0[NextNodeIndex(i,nodeCount)] + h3[NextNodeIndex(i,nodeCount)]);
}
h4[0] = h4[nodeCount];
k4[0] = k4[nodeCount];
for ( i = 0; i <= nodeCount; i++) {
x0[i] = x0[i] + h1[i] / 6 + h2[i] / 3 + h3[i] / 3 + h4[i] / 6;
u0[i] = u0[i] + k1[i] / 6 + k2[i] / 3 + k3[i] / 3 + k4[i] / 6;
}
}
// This method advances the LatticeData object by one step.
public void step(LMView view) { // The view is a parameter in case any info from the view is needed.
numIterations = 3;
for (int i = 0; i < numIterations; i++) {
RungeKuttaLattice(currentDisplacement,currentVelocity,view);
currentTime = currentTime + stepSize;
}
stepNumber++;
}
} // end of class LatticeData
protected void drawTransverse(View3D view){
int i;
Color saveColor = view.getColor();
LMView lmView = (LMView)view;
LatticeData data = lmView.getLatticeData();
double scale = lmView.controlPanel.getScaleFactor();
if (((LMView)view).getShowNormalModeDisplay())
data.GetLatticeModeEnergies((LMView)view);
view.drawLine(0,0,data.latticeLength,0);
view.drawLine(0,-1.6/scale,0,1.6/scale);
view.drawLine(data.latticeLength,-1.6/scale,data.latticeLength,1.6/scale);
for (i=0;i < data.nodeCount; i++)
view.drawLine(data.LatticeAbscissa(i,lmView),data.currentDisplacement[i],data.LatticeAbscissa(i+1,lmView),data.currentDisplacement[i+1]);
view.setColor(Color.BLUE);
for (i=0;i < data.nodeCount; i++)
view.drawLine(data.LatticeAbscissa(i,lmView),data.currentVelocity[i],data.LatticeAbscissa(i+1,lmView),data.currentVelocity[i+1]);
view.setColor(saveColor);
}
protected void doDraw3D(org.freehep.graphics2d.VectorGraphics g, View3D view, Transform3D transform) {
LMView lmView = (LMView)view;
int displayStyle = lmView.getDisplayStyle(); // the view must be of type LMView (defined below)
LatticeData data = lmView.getLatticeData();
double pixelWidth = (data.latticeLength) / (transform.getWidth() - 30); // allows for 15-pixel border on left and right
double xmin = -15*pixelWidth; // TODO: check computation of xmin, xmax, ymin, ymax -- especially for 3D views -- note that 3D views will have to be centered at 0
double xmax = data.latticeLength + 15*pixelWidth;
double scale = 1;
if (displayStyle == DISPLAY_TRANSVERSE){
scale = lmView.controlPanel.getScaleFactor();
double ymax = 2.0 / scale; // TODO: ??? // was 2/scale
double ymin = -ymax;
((LMTransform)transform).resetXYLimits(xmin, xmax, ymin, ymax);
drawTransverse(view);
}
// TODO: What about viewpoints in 3D ???
if (displayStyle == DISPLAY_PENDULUM)
view.setViewPoint(new Vector3D(0,-5*data.latticeLength,0)); // TODO: check viewpoints!
else if (displayStyle == DISPLAY_BRIDGE)
view.setViewPoint(new Vector3D(data.latticeLength,-5*data.latticeLength,data.latticeLength));
// TODO: insert the code to draw the data in the given display style
if (view.getEnableThreeD()) // placeholder code
view.drawString("(3D) Step number = " + data.stepNumber, new Vector3D(0,0,1));
else
view.drawString(" TNE = " + Math.round(100000.0*data.LatticeTotalEnergy(lmView))/100000.0,0,-1.25/scale);
// view.drawString(" TME = " + Math.round(100000.0*data.TotalModalEnergy)/100000.0,0,-1.40/scale);
view.drawString(" Time = " + Math.round(4.0*data.currentTime)/4.0,0,-1.55/scale);
if (((LMView)view).getShowNormalModeDisplay()) {
// TODO: draw the normal mode display ( maybe after additional checks? )
}
}
public View getDefaultView() {
return new LMView();
}
public Animation getCreateAnimation(View view) {
LMView lmView = (LMView)view; // view must be of type LMview
return lmView.new EvolveLattice(true);
}
public ActionList getSettingsCommandsForView(View view) {
ActionList commands = super.getSettingsCommandsForView(view);
Object setXYLimitsCommand = commands.getItem(commands.getItemCount() - 1);
if (setXYLimitsCommand instanceof AbstractActionVMM)
((AbstractActionVMM)setXYLimitsCommand).setEnabled(false);
return commands;
}
public Transform getDefaultTransform(View view) {
return new LMTransform();
}
protected class LMTransform extends Transform3D {
// I need to define this class because resetLimits() is a protected method in
// the Transform class. The resetXYLimits() is used to reset the x and y limits
// in the doDraw() method. This will not work unless preserveAspect and
// applyTransform2D have been turned off, as they are here. This is becuase by the time
// doDraw() is called, these two properties have already been applied to the limits and to
// the graphics context respectively.
public void resetXYLimits(double xmin, double xmax, double ymin, double ymax) {
resetLimits(xmin,xmax,ymin,ymax);
}
}
public class LMView extends View3D {
protected AbstractActionVMM restartAction = new AbstractActionVMM(I18n.tr("vmm.latticemodel.command.Start")) {
public void actionPerformed(ActionEvent evt) {
doRestart();
}
};
protected AbstractActionVMM stopAction = new AbstractActionVMM(I18n.tr("vmm.latticemodel.command.Stop")) {
public void actionPerformed(ActionEvent evt) {
doStop();
}
};
protected AbstractActionVMM continueAction = new AbstractActionVMM(I18n.tr("vmm.latticemodel.command.Continue")) {
public void actionPerformed(ActionEvent evt) {
doContinue();
}
};
protected AbstractActionVMM stepAction = new AbstractActionVMM(I18n.tr("vmm.latticemodel.command.Step")) {
public void actionPerformed(ActionEvent evt) {
doStep();
}
};
protected ActionRadioGroup displayStyleSelect = new ActionRadioGroup() {
public void optionSelected(int selectedIndex) {
setDisplayStyle(getSelectedIndex());
}
};
protected ActionRadioGroup initialModeSelect = new ActionRadioGroup() {
public void optionSelected(int selectedIndex) {
setInitialMode(getSelectedIndex());
}
};
protected ActionRadioGroup initialShapeSelect = new ActionRadioGroup() {
public void optionSelected(int selectedIndex) {
setInitialShape(getSelectedIndex());
}
};
protected ActionRadioGroup boundaryConditionSelect = new ActionRadioGroup() {
public void optionSelected(int selectedIndex) {
setBoundaryCondition(getSelectedIndex());
}
};
protected ToggleAction showNormalModeToggle = new ToggleAction(I18n.tr("vmm.latticemodel.ShowNormalModeDisplay"),canShowNormalModeDisplay) {
public void actionPerformed(ActionEvent evt) {
setShowNormalModeDisplay(getState());
}
};
protected ToggleAction showControlPanelToggle = new ToggleAction(I18n.tr("vmm.latticemodel.ShowControlPanel"),true) {
public void actionPerformed(ActionEvent evt) {
setShowControlPanel(getState());
}
};
protected RealParam latticeLength = new RealParam("vmm.latticeModel.LatticeLength",defaultLatticeLength);
protected RealParam latticeDensity = new RealParam("vmm.latticeModel.LatticeDensity",defaultLatticeDensity);
protected RealParam amplitude = new RealParam("vmm.latticeModel.Amplitude",defaultAmplitude);
protected IntegerParam numberOfNodes = new IntegerParam("vmm.latticeModel.NumberOfNodes",defaultNumberOfNodes) {
public ParameterInput createParameterInput(int inputType) {
return new ParameterInput(this,inputType) {
public String checkContents() {
String contents = getText();
double val = 0;
try {
val = Util.parseConstantExpression(contents).value();
}
catch (ParseError e) {
requestFocus();
selectAll();
return I18n.tr("vmm3d.core.ParameterInput.badExpression", getTitle(), contents, e.getMessage() );
}
int intval;
if (val > 0)
intval = (int)(val + 1e-8);
else
intval = (int)(val - 1e-8);
if (Math.abs(val - intval) > 5e-9) {
requestFocus();
selectAll();
return I18n.tr("vmm3d.core.ParameterInput.badint", getTitle());
}
if (intval < 3 || (intval > 65 &&
intval != 128 && intval != 256)) {
requestFocus();
selectAll();
return I18n.tr("vmm.latticemodel.LatticeModel.badNumberOfNodes");
}
return null;
}
};
}
};
@VMMSave private int displayStyle = DISPLAY_TRANSVERSE;
@VMMSave private int initialShape;
@VMMSave private int initialMode = defaultInitialMode;
@VMMSave private int boundaryCondition;
@VMMSave private boolean showControlPanel = true;
@VMMSave private boolean showNormalModeDisplay = canShowNormalModeDisplay;
protected LatticeData latticeData;
private ControlPanel controlPanel;
public LMView() {
setAntialiased(true);
addParameter(latticeLength);
latticeLength.setMinimumValueForInput(Double.MIN_VALUE);
addParameter(latticeDensity);
addParameter(amplitude);
latticeDensity.setMinimumValueForInput(Double.MIN_VALUE);
addParameter(numberOfNodes);
numberOfNodes.setMinimumValueForInput(3);
numberOfNodes.setMaximumValueForInput(512);
displayStyleSelect.addItem(I18n.tr("vmm.latticemodel.TransverseDisplay"));
displayStyleSelect.addItem(I18n.tr("vmm.latticemodel.LongitudinalDisplay"));
displayStyleSelect.addItem(I18n.tr("vmm.latticemodel.CircularDisplay"));
displayStyleSelect.addItem(I18n.tr("vmm.latticemodel.PendulumDisplay"));
displayStyleSelect.addItem(I18n.tr("vmm.latticemodel.BridgeDisplay"));
displayStyleSelect.setSelectedIndex(defaultDisplayStyle);
initialShapeSelect.addItem(I18n.tr("vmm.latticeModel.SinusoidalInitialShape"));
initialShapeSelect.addItem(I18n.tr("vmm.latticeModel.GaussianInitialShape"));
initialShapeSelect.addItem(I18n.tr("vmm.latticeModel.ThermalInitialShape"));
initialShapeSelect.addItem(I18n.tr("vmm.latticeModel.KinkInitialShape"));
initialShapeSelect.addItem(I18n.tr("vmm.latticeModel.BreatherInitialShape"));
initialShapeSelect.setSelectedIndex(defaultInitialShape);
initialModeSelect.addItem(I18n.tr("vmm.latticeModel.initialMode.First"));
initialModeSelect.addItem(I18n.tr("vmm.latticeModel.initialMode.Second"));
initialModeSelect.addItem(I18n.tr("vmm.latticeModel.initialMode.Fourth"));
initialModeSelect.addItem(I18n.tr("vmm.latticeModel.initialMode.Eighth"));
initialModeSelect.addItem(I18n.tr("vmm.latticeModel.initialMode.Sixteenth"));
initialModeSelect.setSelectedIndex(defaultInitialMode);
boundaryConditionSelect.addItem(I18n.tr("vmm.latticeModel.ZeroBoundaryCondition"));
boundaryConditionSelect.addItem(I18n.tr("vmm.latticeModel.PeriodicBoundaryCondition"));
boundaryConditionSelect.setSelectedIndex(defaultBoundaryCondition);
stopAction.setEnabled(false);
setEnableThreeD(false); // to match default display style
setTransform(new LMTransform()); // this is needed so that the transform used for 2D will be a LMTransform!
showAxesAction.setEnabled(false);
setApplyGraphics2DTransform(false);
setPreserveAspect(false);
}
public int getNumberOfNodes() { // returns a value that is quaranted to be legal
int nodes = numberOfNodes.getValue();
if (nodes < 3)
return 3;
if (nodes <= 64)
return nodes;
if (nodes <= 128)
return 128;
else
return 256;
}
public LatticeData getLatticeData() {
if (latticeData == null)
latticeData = new LatticeData(this);
return latticeData;
}
public int getDisplayStyle() {
return displayStyle;
}
public void setDisplayStyle(int displayStyle) {
if (displayStyle < 0 || displayStyle >= displayStyleSelect.getItemCount())
return; // ignore illegal values
if (this.displayStyle == displayStyle)
return;
this.displayStyle = displayStyle;
displayStyleSelect.setSelectedIndex(displayStyle);
forceRedraw();
boolean is3D = displayStyle == DISPLAY_PENDULUM || displayStyle == DISPLAY_BRIDGE;
boolean changingDimension = is3D != getEnableThreeD();
setEnableThreeD(is3D);
if (is3D && changingDimension) {
setViewStyle(View3D.RED_GREEN_STEREO_VIEW);
setViewpointAction.setEnabled(false); // it's enagled when setEnableThreeD(true) is called
}
if (displayStyle == DISPLAY_TRANSVERSE)
controlPanel.showScaleFactor(I18n.tr("vmm.latticemodel.yScaleFactor"));
else if (displayStyle == DISPLAY_FPU_GRAPH)
controlPanel.showScaleFactor(I18n.tr("vmm.latticemodel.xScaleFactor"));
else
controlPanel.hideScaleFactor();
if (displayStyle == DISPLAY_TRANSVERSE)
controlPanel.setScaleFactor(defaultScaleFactor);
else if (displayStyle == DISPLAY_FPU_GRAPH)
controlPanel.setScaleFactor(0.005);
if (changingDimension && getDisplay() != null)
getDisplay().installMouseTask(getDefaultMouseTask()); // different for 2D and 3D.
}
public int getBoundaryCondition() {
return boundaryCondition;
}
public void setBoundaryCondition(int boundaryCondition) {
if (boundaryCondition != BOUNDARY_CONDITION_ZERO && boundaryCondition != BOUNDARY_CONDITION_PERIODIC)
return; // ignore illegal value
if (this.boundaryCondition == boundaryCondition)
return;
this.boundaryCondition = boundaryCondition;
boundaryConditionSelect.setSelectedIndex(boundaryCondition);
initialConditionChanged();
}
public int getInitialShape() {
return initialShape;
}
public void setInitialShape(int initialShape) {
if (initialShape < 0 || initialShape >= initialShapeSelect.getItemCount())
return; // ignore illegal values
if (this.initialShape == initialShape)
return;
this.initialShape = initialShape;
initialShapeSelect.setSelectedIndex(initialShape);
initialModeSelect.setEnabled(initialShape == INITIAL_SHAPE_SINUSOIDAL);
initialConditionChanged();
}
public int getInitialMode() {
return initialMode;
}
public void setInitialMode(int initialMode) {
if (initialMode < 0 || initialMode >= initialModeSelect.getItemCount())
return; // ignore illegal values
if (this.initialMode == initialMode)
return;
this.initialMode = initialMode;
initialModeSelect.setSelectedIndex(initialMode);
initialConditionChanged();
}
public MouseTask getDefaultMouseTask() {
if (getEnableThreeD())
return null;
else
return new LatticeMouseTask2D();
}
public ActionList getActions() {
ActionList actions = super.getActions();
actions.add(stopAction);
actions.add(continueAction);
actions.add(stepAction);
actions.add(null);
ActionList boundary = new ActionList(I18n.tr("vmm.latticemodel.BoundaryCondition"));
boundary.add(boundaryConditionSelect);
actions.add(boundary);
ActionList initialShp = new ActionList(I18n.tr("vmm.latticemodel.InitialShape"));
initialShp.add(initialShapeSelect);
actions.add(initialShp);
ActionList initialMode = new ActionList(I18n.tr("vmm.latticemodel.InitialMode"));
initialMode.add(initialModeSelect);
actions.add(initialMode);
actions.add(null);
ActionList displayStyle = new ActionList(I18n.tr("vmm.latticemodel.DisplayStyle"));
displayStyle.add(displayStyleSelect);
actions.add(displayStyle);
actions.add(null);
if (canShowNormalModeDisplay)
actions.add(showNormalModeToggle);
actions.add(showControlPanelToggle);
return actions;
}
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(getControlPanel(),BorderLayout.EAST);
getDisplay().getHolder().validate();
}
}
else {
if (getDisplay() != null && controlPanel != null) {
getDisplay().getHolder().remove(controlPanel);
getDisplay().getHolder().validate();
}
}
}
public boolean getShowNormalModeDisplay() {
return showNormalModeDisplay;
}
public void setShowNormalModeDisplay(boolean showNormalModeDisplay) {
if ( ! canShowNormalModeDisplay)
showNormalModeDisplay = false; // don't allow this to be set to true if the exhibit can't show a NormalModeDisplay
if (this.showNormalModeDisplay == showNormalModeDisplay)
return;
this.showNormalModeDisplay = showNormalModeDisplay;
showNormalModeToggle.setState(showNormalModeDisplay);
forceRedraw();
}
public void parameterChanged(Parameter param, Object oldValue,Object newValue) {
super.parameterChanged(param, oldValue, newValue);
initialConditionChanged();
}
public void setDisplay(Display display) {
super.setDisplay(display);
if (display != null)
display.setStopAnimationsOnResize(false);
if (display != null && showControlPanel) {
display.getHolder().add(getControlPanel(),BorderLayout.EAST);
display.getHolder().validate();
}
}
ControlPanel getControlPanel() {
// I don't want to create the control panel when view is first created to give Fermi-Pasta-Ulam
// subclass a chance to add a new display style to displayStyleSelect.
if (controlPanel == null)
controlPanel = new ControlPanel();
return controlPanel;
}
private void initialConditionChanged() {
if (getDisplay() != null)
getDisplay().stopAnimation();
latticeData = null;
restartAction.putValue(Action.NAME, I18n.tr("vmm.latticemodel.command.Start"));
forceRedraw();
}
private void doRestart() {
getDisplay().stopAnimation();
getDisplay().installAnimation(new EvolveLattice(true));
}
private void doStop() {
getDisplay().stopAnimation();
}
private void doStep() {
LatticeData data = getLatticeData(); // getLatticeData() never returns null
data.stepSize = getControlPanel().getStepSize();
// TODO: should the step size really be obtained from the control panel at each step instead of just at the start?
data.step(this);
forceRedraw();
}
private void doContinue() {
getDisplay().installAnimation(new EvolveLattice(false));
}
private class LatticeMouseTask2D extends MouseTask {
int startY;
int startX;
double initialScaleFactor;
boolean dragging;
public boolean doMouseDown(MouseEvent evt, Display display, View view, int width, int height) {
if (!controlPanel.isScaleFactorShown()) {
dragging = false;
return false;
}
startX = evt.getX();
startY = evt.getY();
initialScaleFactor = controlPanel.getScaleFactor();
dragging = true;
return true;
}
public void doMouseDrag(MouseEvent evt, Display display, View view, int width, int height) {
if (! dragging)
return;
double multiplier = Math.exp((startY - evt.getY() + (evt.getX() - startX))/250.0);
controlPanel.setScaleFactor(initialScaleFactor * multiplier);
forceRedraw();
}
public void doMouseUp(MouseEvent evt, Display display, View view, int width, int height) {
dragging = false;
}
}
private class EvolveLattice extends TimerAnimation {
boolean create;
EvolveLattice(boolean create) {
setMillisecondsPerFrame(5); // TODO: Check speed!
this.create = create;
}
protected void drawFrame() {
doStep();
}
protected void animationStarting() {
if (create)
latticeData = null; // forces data to be recomputed based on settings for initial conditions
continueAction.setEnabled(false);
stepAction.setEnabled(false);
stopAction.setEnabled(true);
restartAction.putValue(Action.NAME, I18n.tr("vmm.latticemodel.command.Restart"));
}
protected void animationEnding() {
continueAction.setEnabled(true);
stepAction.setEnabled(true);
stopAction.setEnabled(false);
}
}
protected class ControlPanel extends JPanel {
JTextField stepSizeInput;
JTextField scaleFactorInput;
boolean scaleFactorShown = true;
JLabel scaleFactorLabel;
JPanel scaleFactorPanel;
JPanel componentPanel;
Font font;
ControlPanel() {
setBorder(BorderFactory.createLineBorder(Color.BLACK,1));
componentPanel = new JPanel();
componentPanel.setLayout(new GridLayout(0,1));
add(componentPanel);
font = new Font("SansSerif", Font.BOLD, 10);
Font inputFont = new Font("SansSerif", Font.PLAIN, 10);
JButton restartButton = new JButton(restartAction);
restartButton.setFont(font);
componentPanel.add(restartButton);
JButton stopButton = new JButton(stopAction);
stopButton.setFont(font);
componentPanel.add(stopButton);
JButton continueButton = new JButton(continueAction);
continueButton.setFont(font);
componentPanel.add(continueButton);
JButton stepButton = new JButton(stepAction);
stepButton.setFont(font);
componentPanel.add(stepButton);
componentPanel.add(Box.createVerticalStrut(1));
stepSizeInput = new JTextField("" + defaultStepSize, 6);
stepSizeInput.setFont(inputFont);
JLabel lbl = new JLabel(I18n.tr("vmm.latticemodel.StepSize") + "=");
lbl.setFont(font);
JPanel p = new JPanel();
p.add(lbl);
p.add(stepSizeInput);
componentPanel.add(p);
scaleFactorInput = new JTextField("" + defaultScaleFactor, 5);
scaleFactorInput.setFont(inputFont);
scaleFactorLabel = new JLabel(I18n.tr("vmm.latticemodel.yScaleFactor") + "=");
scaleFactorLabel.setFont(font);
scaleFactorPanel = new JPanel();
scaleFactorPanel.add(scaleFactorLabel);
scaleFactorPanel.add(scaleFactorInput);
componentPanel.add(scaleFactorPanel);
JRadioButton[] displaySelectButtons = displayStyleSelect.createRadioButtons();
for (JRadioButton rb : displaySelectButtons) {
rb.setFont(font);
componentPanel.add(rb);
}
componentPanel.add(Box.createVerticalStrut(1));
JButton b = new JButton(I18n.tr("vmm.latticemodel.setParams"));
b.setFont(font);
componentPanel.add(b);
b.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent evt) {
getDisplay().stopAnimation();
Parameter[] params = getViewAndExhibitParameters();
ParameterDialog.showDialog(getDisplay(),I18n.tr("vmm3d.core.dialogtitle.SetParameters"),params);
}
});
}
double getStepSize() { // get step size from input box, but return default step size if input is bad
try {
double d = Double.parseDouble(stepSizeInput.getText().trim());
if (d <= 0)
throw new NumberFormatException();
return d;
}
catch (NumberFormatException e) {
stepSizeInput.setText("" + defaultStepSize);
return defaultStepSize;
}
}
double getScaleFactor() { // get scaleFactor from input box, but return default scale factor if input is bad
try {
double d = Double.parseDouble(scaleFactorInput.getText().trim());
if (d <= 0)
throw new NumberFormatException();
return d;
}
catch (NumberFormatException e) {
scaleFactorInput.setText("" + defaultScaleFactor);
return defaultScaleFactor;
}
}
void setScaleFactor(double scaleFactor) {
String str = String.format("%.4f", scaleFactor);
scaleFactorInput.setText(str);
}
boolean isScaleFactorShown() {
return scaleFactorShown;
}
void hideScaleFactor() {
if (scaleFactorShown) {
componentPanel.remove(6);
componentPanel.add(Box.createVerticalStrut(1), 6);
scaleFactorShown = false;
validate();
}
}
void showScaleFactor(String label) {
scaleFactorLabel.setText(label);
if (! scaleFactorShown) {
componentPanel.remove(6);
componentPanel.add(scaleFactorPanel, 6);
scaleFactorShown = true;
}
validate();
}
}
}
}
Ads help maintain this website.