major optimization updates
authorplaa <plaa@180e2498-e6e9-4542-8430-84ac67f01cd8>
Sun, 7 Aug 2011 16:54:36 +0000 (16:54 +0000)
committerplaa <plaa@180e2498-e6e9-4542-8430-84ac67f01cd8>
Sun, 7 Aug 2011 16:54:36 +0000 (16:54 +0000)
git-svn-id: https://openrocket.svn.sourceforge.net/svnroot/openrocket/trunk@147 180e2498-e6e9-4542-8430-84ac67f01cd8

93 files changed:
ChangeLog
l10n/messages.properties
src/net/sf/openrocket/aerodynamics/BarrowmanCalculator.java
src/net/sf/openrocket/aerodynamics/FlightConditions.java
src/net/sf/openrocket/aerodynamics/barrowman/FinSetCalc.java
src/net/sf/openrocket/aerodynamics/barrowman/SymmetricComponentCalc.java
src/net/sf/openrocket/document/OpenRocketDocument.java
src/net/sf/openrocket/document/Simulation.java
src/net/sf/openrocket/file/openrocket/OpenRocketLoader.java
src/net/sf/openrocket/file/openrocket/OpenRocketSaver.java
src/net/sf/openrocket/gui/adaptors/BooleanModel.java
src/net/sf/openrocket/gui/adaptors/DoubleModel.java
src/net/sf/openrocket/gui/components/CsvOptionPanel.java [new file with mode: 0644]
src/net/sf/openrocket/gui/components/DescriptionArea.java
src/net/sf/openrocket/gui/components/DoubleCellEditor.java [new file with mode: 0644]
src/net/sf/openrocket/gui/components/SimulationExportPanel.java
src/net/sf/openrocket/gui/components/UnitCellEditor.java
src/net/sf/openrocket/gui/dialogs/MotorDatabaseLoadingDialog.java
src/net/sf/openrocket/gui/dialogs/optimization/FunctionEvaluationData.java [new file with mode: 0644]
src/net/sf/openrocket/gui/dialogs/optimization/GeneralOptimizationDialog.java
src/net/sf/openrocket/gui/dialogs/optimization/OptimizationPlotDialog.java [new file with mode: 0644]
src/net/sf/openrocket/gui/dialogs/optimization/OptimizationStepData.java [new file with mode: 0644]
src/net/sf/openrocket/gui/dialogs/optimization/OptimizationWorker.java [new file with mode: 0644]
src/net/sf/openrocket/gui/dialogs/optimization/SimulationModifierTree.java
src/net/sf/openrocket/gui/main/BasicFrame.java
src/net/sf/openrocket/gui/main/SimulationEditDialog.java
src/net/sf/openrocket/gui/main/SimulationRunDialog.java
src/net/sf/openrocket/gui/optimization/OptimizationTestDialog.java [deleted file]
src/net/sf/openrocket/gui/plot/PlotConfiguration.java
src/net/sf/openrocket/gui/plot/PlotDialog.java [deleted file]
src/net/sf/openrocket/gui/plot/SimulationPlotDialog.java [new file with mode: 0644]
src/net/sf/openrocket/gui/plot/SimulationPlotPanel.java
src/net/sf/openrocket/gui/print/DesignReport.java
src/net/sf/openrocket/gui/rocketfigure/SymmetricComponentShapes.java
src/net/sf/openrocket/gui/scalefigure/AbstractScaleFigure.java
src/net/sf/openrocket/gui/scalefigure/FinPointFigure.java
src/net/sf/openrocket/gui/scalefigure/RocketFigure.java
src/net/sf/openrocket/gui/scalefigure/RocketPanel.java
src/net/sf/openrocket/gui/scalefigure/ScaleFigure.java
src/net/sf/openrocket/gui/scalefigure/ScaleScrollPane.java
src/net/sf/openrocket/models/wind/PinkNoiseWindModel.java
src/net/sf/openrocket/optimization/general/Point.java
src/net/sf/openrocket/optimization/general/multidim/MultidirectionalSearchOptimizer.java
src/net/sf/openrocket/optimization/general/multidim/SearchPattern.java
src/net/sf/openrocket/optimization/rocketoptimization/OptimizableParameter.java
src/net/sf/openrocket/optimization/rocketoptimization/RocketOptimizationFunction.java
src/net/sf/openrocket/optimization/rocketoptimization/RocketOptimizationListener.java
src/net/sf/openrocket/optimization/rocketoptimization/SimulationDomain.java
src/net/sf/openrocket/optimization/rocketoptimization/SimulationModifier.java
src/net/sf/openrocket/optimization/rocketoptimization/domains/IdentitySimulationDomain.java
src/net/sf/openrocket/optimization/rocketoptimization/domains/StabilityDomain.java
src/net/sf/openrocket/optimization/rocketoptimization/modifiers/AbstractSimulationModifier.java
src/net/sf/openrocket/optimization/rocketoptimization/modifiers/GenericComponentModifier.java
src/net/sf/openrocket/optimization/rocketoptimization/modifiers/GenericModifier.java
src/net/sf/openrocket/optimization/rocketoptimization/parameters/GroundHitVelocityParameter.java [new file with mode: 0644]
src/net/sf/openrocket/optimization/rocketoptimization/parameters/LandingDistanceParameter.java [new file with mode: 0644]
src/net/sf/openrocket/optimization/rocketoptimization/parameters/MaximumAccelerationParameter.java [new file with mode: 0644]
src/net/sf/openrocket/optimization/rocketoptimization/parameters/MaximumAltitudeParameter.java
src/net/sf/openrocket/optimization/rocketoptimization/parameters/MaximumVelocityParameter.java [new file with mode: 0644]
src/net/sf/openrocket/optimization/rocketoptimization/parameters/StabilityParameter.java [new file with mode: 0644]
src/net/sf/openrocket/optimization/services/DefaultOptimizableParameterService.java
src/net/sf/openrocket/optimization/services/DefaultSimulationModifierService.java
src/net/sf/openrocket/rocketcomponent/Configuration.java
src/net/sf/openrocket/rocketcomponent/FinSet.java
src/net/sf/openrocket/rocketcomponent/MassComponent.java
src/net/sf/openrocket/rocketcomponent/Parachute.java
src/net/sf/openrocket/rocketcomponent/Streamer.java
src/net/sf/openrocket/rocketcomponent/SymmetricComponent.java
src/net/sf/openrocket/rocketcomponent/Transition.java
src/net/sf/openrocket/simulation/BasicEventSimulationEngine.java
src/net/sf/openrocket/simulation/GUISimulationConditions.java [deleted file]
src/net/sf/openrocket/simulation/RK4SimulationStepper.java
src/net/sf/openrocket/simulation/SimulationConditions.java
src/net/sf/openrocket/simulation/SimulationOptions.java [new file with mode: 0644]
src/net/sf/openrocket/simulation/exception/MotorIgnitionException.java [new file with mode: 0644]
src/net/sf/openrocket/simulation/exception/SimulationLaunchException.java
src/net/sf/openrocket/startup/Application.java
src/net/sf/openrocket/unit/CaliberUnit.java
src/net/sf/openrocket/unit/UnitGroup.java
src/net/sf/openrocket/unit/Value.java
src/net/sf/openrocket/util/Chars.java
src/net/sf/openrocket/util/Coordinate.java
src/net/sf/openrocket/util/FileHelper.java
src/net/sf/openrocket/util/GUIUtil.java
src/net/sf/openrocket/util/MathUtil.java
src/net/sf/openrocket/util/MutableCoordinate.java [deleted file]
src/net/sf/openrocket/util/Prefs.java
src/net/sf/openrocket/util/Quaternion.java
test/net/sf/openrocket/optimization/rocketoptimization/TestRocketOptimizationFunction.java
test/net/sf/openrocket/optimization/rocketoptimization/modifiers/TestGenericModifier.java
test/net/sf/openrocket/unit/ValueTest.java
test/net/sf/openrocket/util/TextUtilTest.java
web/html/techdoc.pdf

index 8baecf09810fc915ece3aff536efa2e8c353636b..3b3ed8f1a4706d0b7f6299b280f82828d46a0811 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,11 @@
+2011-08-07  Sampo Niskanen
+
+       * Optimization implementation
+
+2011-07-29  Sampo Niskanen
+
+       * [BUG] NaN in transition radius calculation
+
 2011-07-22  Sampo Niskanen
 
        * Released version 1.1.6.
index 9cfea75d0330ca7b158a7a6722e2090169c83f5d..8d71278d96412cbce1a9c4b0c2f23da14fb31a01 100644 (file)
@@ -79,6 +79,7 @@ error.writing.desc = An error occurred while writing to the file:
 
 
 ! Labels used in buttons of dialog windows
+# TODO: Rename these to "btn.xxx"
 button.ok = OK
 button.cancel = Cancel
 button.close = Close
@@ -415,6 +416,12 @@ SimExpPan.ExportingVar.desc3 = variables out of
 SimExpPan.Col.Variable = Variable
 SimExpPan.Col.Unit = Unit
 
+
+CsvOptionPanel.separator.space = SPACE
+CsvOptionPanel.separator.tab = TAB
+
+
+
 ! MotorPlot
 MotorPlot.title.Motorplot = Motor plot
 MotorPlot.but.Select = Select
@@ -919,7 +926,7 @@ TCMotorSelPan.delayBox.None = None
 PlotDialog.title.Flightdataplot = Flight data plot
 PlotDialog.Chart.Simulatedflight = Simulated flight
 PlotDialog.CheckBox.Showdatapoints = Show data points
-PlotDialog.lbl.Chart = Click+drag down+right to zoom in, up+left to zoom out
+PlotDialog.lbl.Chart = Click and drag down+right to zoom in, up+left to zoom out
 
 
 ! "main" prefix is used for the main application dialog
@@ -964,6 +971,8 @@ main.menu.analyze = Analyze
 main.menu.analyze.desc = Rocket analysis
 main.menu.analyze.componentAnalysis = Component analysis
 main.menu.analyze.componentAnalysis.desc = Analyze the rocket components separately
+main.menu.analyze.optimization = Rocket optimization
+main.menu.analyze.optimization.desc = General rocket design optimization
 
 main.menu.help = Help
 main.menu.help.desc = Information about OpenRocket
@@ -1296,17 +1305,177 @@ EllipticalFinSet.Ellipticalfinset = Elliptical fin set
 
 ! Modifiers
 
-optimization.modifier.nosecone.length = Nose cone length
-optimization.modifier.nosecone.diameter = Nose cone diameter
-optimization.modifier.nosecone.thickness = Nose cone thickness
-               
-optimization.modifier.transition.length = Transition length
-optimization.modifier.transition.forediameter = Transition fore diameter
-optimization.modifier.transition.aftdiameter = Transition aft diameter
-optimization.modifier.transition.thickness = Transition thickness
-
-optimization.modifier.bodytube.length = Body tube length
-optimization.modifier.bodytube.outerDiameter = Body tube outer diameter
-optimization.modifier.bodytube.innerDiameter = Body tube inner diameter
-optimization.modifier.bodytube.thickness = Body tube thickness
-
+optimization.modifier.nosecone.length = Length
+optimization.modifier.nosecone.length.desc = Optimize the nose cone length.
+optimization.modifier.nosecone.diameter = Diameter
+optimization.modifier.nosecone.diameter.desc = Optimize the nose cone base diameter.
+optimization.modifier.nosecone.thickness = Thickness
+optimization.modifier.nosecone.thickness.desc = Optimize the nose cone wall thickness.
+optimization.modifier.nosecone.shapeparameter = Shape parameter
+optimization.modifier.nosecone.shapeparameter.desc = Optimize the nose cone shape parameter.
+
+optimization.modifier.transition.length = Length
+optimization.modifier.transition.length.desc = Optimize the transition length.
+optimization.modifier.transition.forediameter = Fore diameter
+optimization.modifier.transition.forediameter.desc = Optimize the transition fore diameter.
+optimization.modifier.transition.aftdiameter = Aft diameter
+optimization.modifier.transition.aftdiameter.desc = Optimize the transition aft diameter.
+optimization.modifier.transition.thickness = Thickness
+optimization.modifier.transition.thickness.desc = Optimize the transition wall thickness.
+optimization.modifier.transition.shapeparameter = Shape parameter
+optimization.modifier.transition.shapeparameter.desc = Optimize the transition shape parameter.
+
+optimization.modifier.bodytube.length = Length
+optimization.modifier.bodytube.length.desc = Optimize the body tube length.
+optimization.modifier.bodytube.outerDiameter = Outer diameter
+optimization.modifier.bodytube.outerDiameter.desc = Optimize the body tube outer diameter while maintaining the wall thickness.
+optimization.modifier.bodytube.thickness = Thickness
+optimization.modifier.bodytube.thickness.desc = Optimize the body tube wall thickness.
+
+optimization.modifier.trapezoidfinset.rootChord = Root chord
+optimization.modifier.trapezoidfinset.rootChord.desc = Optimize the root chord length of the fin set (length of fin at the rocket body).
+optimization.modifier.trapezoidfinset.tipChord = Tip chord
+optimization.modifier.trapezoidfinset.tipChord.desc = Optimize the tip chord length of the fin set (length of fin at outer edge).
+optimization.modifier.trapezoidfinset.sweep  = Sweep
+optimization.modifier.trapezoidfinset.sweep.desc = Optimize the sweep of the fin set (distance that the leading edge sweeps backwards).
+optimization.modifier.trapezoidfinset.height = Height
+optimization.modifier.trapezoidfinset.height.desc = Optimize the height (semi-span) of the fin set.
+
+optimization.modifier.ellipticalfinset.length = Root chord
+optimization.modifier.ellipticalfinset.length.desc = Optimize the root chord length of the fin set.
+optimization.modifier.ellipticalfinset.height = Height
+optimization.modifier.ellipticalfinset.height = Optimize the height (semi-span) of the fin set.
+
+optimization.modifier.finset.cant = Cant angle
+optimization.modifier.finset.cant.desc = Optimize the cant angle of the fin set.
+optimization.modifier.finset.position = Position
+optimization.modifier.finset.position.desc = Optimize the fin set position along the rocket body.
+
+optimization.modifier.launchlug.length = Length
+optimization.modifier.launchlug.length.desc = Optimize the launch lug length.
+optimization.modifier.launchlug.outerDiameter = Outer diameter
+optimization.modifier.launchlug.outerDiameter.desc = Optimize the outer diameter of the launch lug.
+optimization.modifier.launchlug.thickness = Thickness
+optimization.modifier.launchlug.thickness.desc = Optimize the launch lug thickness while keeping the outer diameter constant.
+optimization.modifier.launchlug.position = Position
+optimization.modifier.launchlug.position.desc = Optimize the launch lug position along the rocket body.
+
+
+optimization.modifier.internalcomponent.position = Position
+optimization.modifier.internalcomponent.position.desc = Optimize the position of the component relative to the parent component.
+
+optimization.modifier.masscomponent.mass = Mass
+optimization.modifier.masscomponent.mass.desc = Optimize the mass of the mass component.
+
+optimization.modifier.parachute.diameter = Diameter
+optimization.modifier.parachute.diameter.desc = Optimize the parachute canopy diameter.
+optimization.modifier.parachute.coefficient = Drag coefficient
+optimization.modifier.parachute.coefficient.desc = Optimize the drag coefficient of the parachute.  Typical parachutes have a drag coefficient of about 0.8.
+
+optimization.modifier.streamer.length = Length
+optimization.modifier.streamer.length.desc = Optimize the length of the streamer.
+optimization.modifier.streamer.width = Width
+optimization.modifier.streamer.width.desc = Optimize the width of the streamer.
+optimization.modifier.streamer.aspectRatio = Aspect ratio
+optimization.modifier.streamer.aspectRatio.desc = Optimize the aspect ratio of the streamer (length/width).  You should NOT select streamer length or width at the same time with the aspect ratio.
+optimization.modifier.streamer.coefficient = Drag coefficient
+optimization.modifier.streamer.coefficient.desc = Optimize the drag coefficient of the streamer.
+
+optimization.modifier.recoverydevice.deployDelay = Deployment delay
+optimization.modifier.recoverydevice.deployDelay.desc = Optimize the deployment delay of the recovery device.
+optimization.modifier.recoverydevice.deployAltitude = Deployment altitude
+optimization.modifier.recoverydevice.deployAltitude.desc = Optimize the deployment altitude of the recovery device.
+
+optimization.modifier.rocketcomponent.overrideMass = Override mass
+optimization.modifier.rocketcomponent.overrideMass.desc = Optimize the overridden mass of the component.
+optimization.modifier.rocketcomponent.overrideCG = Override CG
+optimization.modifier.rocketcomponent.overrideCG.desc = Optimize the overridden center of gravity of the component.
+
+optimization.modifier.motormount.overhang = Motor overhang
+optimization.modifier.motormount.overhang.desc = Optimize the motor overhang.
+optimization.modifier.motormount.delay = Motor ignition delay
+optimization.modifier.motormount.delay.desc = Optimize the motor ignition delay.
+
+
+
+
+! General rocket design optimization dialog
+
+GeneralOptimizationDialog.title = Rocket optimization
+GeneralOptimizationDialog.goal.maximize = Maximize value
+GeneralOptimizationDialog.goal.minimize = Minimize value
+GeneralOptimizationDialog.goal.seek = Seek value of
+GeneralOptimizationDialog.btn.start = Start optimization
+GeneralOptimizationDialog.btn.stop = Stop optimization
+GeneralOptimizationDialog.lbl.paramsToOptimize = Parameters to optimize:
+GeneralOptimizationDialog.btn.add = Add
+GeneralOptimizationDialog.btn.add.ttip = Add the selected parameter to the optimization
+GeneralOptimizationDialog.btn.remove = Remove
+GeneralOptimizationDialog.btn.remove.ttip = Remove the selected parameter from the optimization
+GeneralOptimizationDialog.btn.removeAll = Remove all
+GeneralOptimizationDialog.btn.removeAll.ttip = Remove all parameters from the optimization
+GeneralOptimizationDialog.lbl.availableParams = Available parameters:
+GeneralOptimizationDialog.lbl.optimizationOpts = Optimization options
+GeneralOptimizationDialog.lbl.optimizeSim = Optimize simulation:
+GeneralOptimizationDialog.lbl.optimizeSim.ttip = Select which simulation to optimize
+GeneralOptimizationDialog.lbl.optimizeValue = Optimized value:
+GeneralOptimizationDialog.lbl.optimizeValue.ttip = Select what value is to be optimized
+GeneralOptimizationDialog.lbl.optimizeGoal = Optimization goal:
+GeneralOptimizationDialog.lbl.optimizeGoal.ttip = Select the goal of the optimization
+GeneralOptimizationDialog.lbl.optimizeGoalValue.ttip = Custom value to seek
+GeneralOptimizationDialog.lbl.requireStability = Required stability
+GeneralOptimizationDialog.lbl.requireMinStability = Minimum stability:
+GeneralOptimizationDialog.lbl.requireMinStability.ttip = Require a minimum static stability margin for the design
+GeneralOptimizationDialog.lbl.requireMaxStability = Maximum stability:
+GeneralOptimizationDialog.lbl.requireMaxStability.ttip = Require a maximum static stability margin for the design
+GeneralOptimizationDialog.status.bestValue = Best value:
+GeneralOptimizationDialog.status.bestValue.ttip = Best optimization value found so far.
+GeneralOptimizationDialog.status.stepCount = Step count:
+GeneralOptimizationDialog.status.stepCount.ttip = Number of optimization steps that have been performed.
+GeneralOptimizationDialog.status.evalCount = Evaluations:
+GeneralOptimizationDialog.status.evalCount.ttip = Total number of function evaluations (simulations) that have been performed.
+GeneralOptimizationDialog.status.stepSize = Step size:
+GeneralOptimizationDialog.status.stepSize.ttip = Current optimization step size (relative to the optimization parameter ranges)
+GeneralOptimizationDialog.btn.plotPath = Plot path
+GeneralOptimizationDialog.btn.plotPath.ttip = Plot the optimization path (one and two dimensional optimization only)
+GeneralOptimizationDialog.btn.save = Save path
+GeneralOptimizationDialog.btn.save.ttip = Save the results of the function evaluations (simulations) as a CSV file.
+GeneralOptimizationDialog.btn.apply = Apply optimization
+GeneralOptimizationDialog.btn.apply.ttip = Apply the optimization results to the rocket design
+GeneralOptimizationDialog.btn.reset = Reset
+GeneralOptimizationDialog.btn.reset.ttip = Reset the rocket design to the current rocket design
+GeneralOptimizationDialog.btn.close = Close
+GeneralOptimizationDialog.btn.close.ttip = Close the dialog without modifying the rocket design
+GeneralOptimizationDialog.error.selectParams.text = First select some parameters to optimize from the available parameters.
+GeneralOptimizationDialog.error.selectParams.title = Select optimization parameters
+GeneralOptimizationDialog.error.optimizationFailure.text = The optimization failed to run:
+GeneralOptimizationDialog.error.optimizationFailure.title = Optimization failed
+GeneralOptimizationDialog.undoText = Apply optimization
+GeneralOptimizationDialog.basicSimulationName = Basic simulation
+GeneralOptimizationDialog.noSimulationName = No simulation
+GeneralOptimizationDialog.table.col.parameter = Parameter
+GeneralOptimizationDialog.table.col.current = Current
+GeneralOptimizationDialog.table.col.min = Minimum
+GeneralOptimizationDialog.table.col.max = Maximum
+GeneralOptimizationDialog.export.header = Include header line
+GeneralOptimizationDialog.export.header.ttip = Include a header line as the first line containing the field descriptions.
+GeneralOptimizationDialog.export.stability = Stability
+
+
+! Dialog for plotting optimization results
+OptimizationPlotDialog.title = Optimization results
+OptimizationPlotDialog.lbl.zoomInstructions = Click and drag down+right to zoom in, up+left to zoom out
+OptimizationPlotDialog.plot1d.title = Optimization result
+OptimizationPlotDialog.plot1d.series = Optimization result
+OptimizationPlotDialog.plot2d.title = Optimization path
+OptimizationPlotDialog.plot2d.path = Optimization path
+OptimizationPlotDialog.plot2d.evals = Evaluations
+OptimizationPlotDialog.plot.ttip.stability = Stability:
+
+! Optimization parameters
+MaximumAltitudeParameter.name = Apogee altitude
+MaximumVelocityParameter.name = Maximum velocity
+MaximumAccelerationParameter.name = Maximum acceleration
+StabilityParameter.name = Stability
+GroundHitVelocityParameter.name = Ground hit speed
+LandingDistanceParameter.name = Landing distance
index 4b243389a418f8132cc3e4a1e59bf962c075fbc9..ecafae1e46922163ed6ef3c7b47037564f475c20 100644 (file)
@@ -12,10 +12,10 @@ import net.sf.openrocket.aerodynamics.barrowman.FinSetCalc;
 import net.sf.openrocket.aerodynamics.barrowman.RocketComponentCalc;
 import net.sf.openrocket.rocketcomponent.Configuration;
 import net.sf.openrocket.rocketcomponent.ExternalComponent;
+import net.sf.openrocket.rocketcomponent.ExternalComponent.Finish;
 import net.sf.openrocket.rocketcomponent.FinSet;
 import net.sf.openrocket.rocketcomponent.RocketComponent;
 import net.sf.openrocket.rocketcomponent.SymmetricComponent;
-import net.sf.openrocket.rocketcomponent.ExternalComponent.Finish;
 import net.sf.openrocket.util.Coordinate;
 import net.sf.openrocket.util.MathUtil;
 import net.sf.openrocket.util.PolyInterpolator;
@@ -268,7 +268,7 @@ public class BarrowmanCalculator extends AbstractAerodynamicCalculator {
                                //                              System.out.printf("constant Cf=%f ",Cf);
                        } else if (Re < 5.39e5) {
                                // Fully laminar
-                               Cf = 1.328 / Math.sqrt(Re);
+                               Cf = 1.328 / MathUtil.safeSqrt(Re);
                                //                              System.out.printf("basic Cf=%f ",Cf);
                        } else {
                                // Transitional
index 64e814559f58c812801107c11b8c7ee4419c30dc..45b9d20f4a213ce861b8d9ed273cbd13ec41ec94 100644 (file)
@@ -57,7 +57,7 @@ public class FlightConditions implements Cloneable, ChangeSource, Monitorable {
         * Sqrt(1 - M^2)  for M<1
         * Sqrt(M^2 - 1)  for M>1
         */
-       private double beta = Math.sqrt(1 - mach * mach);
+       private double beta = MathUtil.safeSqrt(1 - mach * mach);
        
 
        /** Current roll rate. */
@@ -122,7 +122,7 @@ public class FlightConditions implements Cloneable, ChangeSource, Monitorable {
         */
        public void setRefArea(double area) {
                refArea = area;
-               refLength = Math.sqrt(area / Math.PI) * 2;
+               refLength = MathUtil.safeSqrt(area / Math.PI) * 2;
                fireChangeEvent();
        }
        
@@ -237,9 +237,9 @@ public class FlightConditions implements Cloneable, ChangeSource, Monitorable {
                
                this.mach = mach;
                if (mach < 1)
-                       this.beta = Math.sqrt(1 - mach * mach);
+                       this.beta = MathUtil.safeSqrt(1 - mach * mach);
                else
-                       this.beta = Math.sqrt(mach * mach - 1);
+                       this.beta = MathUtil.safeSqrt(mach * mach - 1);
                fireChangeEvent();
        }
        
@@ -378,6 +378,7 @@ public class FlightConditions implements Cloneable, ChangeSource, Monitorable {
         * 
         * @return      the number of times this object has been modified since instantiation.
         */
+       @Override
        public int getModID() {
                return modID + modIDadd + this.atmosphericConditions.getModID();
        }
index 2b3d7a6a80739583a39702c76d91f87cfe20ceaf..bd8ee75e52f80985be18967b8feb631c0edfc6da 100644 (file)
@@ -1,6 +1,6 @@
 package net.sf.openrocket.aerodynamics.barrowman;
 
-import static java.lang.Math.*;
+import static java.lang.Math.pow;
 import static net.sf.openrocket.util.MathUtil.pow2;
 
 import java.util.Arrays;
@@ -462,7 +462,7 @@ public class FinSetCalc extends RocketComponentCalc {
                double[] k3 = new double[n];
                for (int i = 0; i < n; i++) {
                        double M = CNA_SUPERSONIC + i * 0.1;
-                       double beta = sqrt(M * M - 1);
+                       double beta = MathUtil.safeSqrt(M * M - 1);
                        x[i] = M;
                        k1[i] = 2.0 / beta;
                        k2[i] = ((GAMMA + 1) * pow(M, 4) - 4 * pow2(beta)) / (4 * pow(beta, 4));
@@ -487,7 +487,7 @@ public class FinSetCalc extends RocketComponentCalc {
                
                // Subsonic case
                if (mach <= CNA_SUBSONIC) {
-                       return 2 * Math.PI * pow2(span) / (1 + sqrt(1 + (1 - pow2(mach)) *
+                       return 2 * Math.PI * pow2(span) / (1 + MathUtil.safeSqrt(1 + (1 - pow2(mach)) *
                                        pow2(pow2(span) / (finArea * cosGamma)))) / ref;
                }
                
@@ -501,7 +501,7 @@ public class FinSetCalc extends RocketComponentCalc {
                double subV, superV;
                double subD, superD;
                
-               double sq = sqrt(1 + (1 - pow2(CNA_SUBSONIC)) * pow2(span * span / (finArea * cosGamma)));
+               double sq = MathUtil.safeSqrt(1 + (1 - pow2(CNA_SUBSONIC)) * pow2(span * span / (finArea * cosGamma)));
                subV = 2 * Math.PI * pow2(span) / ref / (1 + sq);
                subD = 2 * mach * Math.PI * pow(span, 6) / (pow2(finArea * cosGamma) * ref *
                                sq * pow2(1 + sq));
index 42bce5474dbf68c302f02c03c60964930322d659..23178a33b8a58d822f3f9e559541efd355ab7275 100644 (file)
@@ -317,14 +317,14 @@ public class SymmetricComponentCalc extends RocketComponentCalc {
                                p = (param - 0.5) * 4;
                        } else {
                                int1 = x34Interpolator;
-                               int2 = calculateOgiveNoseInterpolator(0, 1 / Math.sqrt(1 + 4 * pow2(fineness)));
+                               int2 = calculateOgiveNoseInterpolator(0, 1 / MathUtil.safeSqrt(1 + 4 * pow2(fineness)));
                                p = (param - 0.75) * 4;
                        }
                        break;
                
                case PARABOLIC:
                        if (param <= 0.5) {
-                               int1 = calculateOgiveNoseInterpolator(0, 1 / Math.sqrt(1 + 4 * pow2(fineness)));
+                               int1 = calculateOgiveNoseInterpolator(0, 1 / MathUtil.safeSqrt(1 + 4 * pow2(fineness)));
                                int2 = parabolic12Interpolator;
                                p = param * 2;
                        } else if (param <= 0.75) {
@@ -429,7 +429,7 @@ public class SymmetricComponentCalc extends RocketComponentCalc {
                
                // Above M = 1.3 use direct formula
                for (double m = 1.32; m < 4; m += 0.02) {
-                       interpolator.addPoint(m, mul * (2.1 * pow2(sinphi) + 0.5 * sinphi / Math.sqrt(m * m - 1)));
+                       interpolator.addPoint(m, mul * (2.1 * pow2(sinphi) + 0.5 * sinphi / MathUtil.safeSqrt(m * m - 1)));
                }
                
                return interpolator;
index d0daa17a6a8ea5464b5201268c4d29e8129b8df5..c935bd294e0d5ca12e113a480390924ef43614bc 100644 (file)
@@ -26,14 +26,20 @@ import net.sf.openrocket.util.Icons;
 
 /**
  * Class describing an entire OpenRocket document, including a rocket and
- * simulations.  This class also handles undo/redo operations for the rocket structure.
+ * simulations.  The document contains:
+ * <p>
+ * - the rocket definition
+ * - a default Configuration
+ * - Simulation instances
+ * - the stored file and file save information
+ * - undo/redo information
  * 
  * @author Sampo Niskanen <sampo.niskanen@iki.fi>
  */
 public class OpenRocketDocument implements ComponentChangeListener {
        private static final LogHelper log = Application.getLogger();
        private static final Translator trans = Application.getTranslator();
-
+       
        /**
         * The minimum number of undo levels that are stored.
         */
@@ -49,6 +55,8 @@ public class OpenRocketDocument implements ComponentChangeListener {
        /** Whether an undo error has already been reported to the user */
        private static boolean undoErrorReported = false;
        
+
+
        private final Rocket rocket;
        private final Configuration configuration;
        
@@ -496,6 +504,27 @@ public class OpenRocketDocument implements ComponentChangeListener {
                }
        }
        
+       
+
+       /**
+        * Return a copy of this document.  The rocket is copied with original ID's, the default
+        * motor configuration ID is maintained and the simulations are copied to the new rocket.
+        * No undo/redo information or file storage information is maintained.
+        * 
+        * @return      a copy of this document.
+        */
+       public OpenRocketDocument copy() {
+               Rocket rocketCopy = rocket.copyWithOriginalID();
+               OpenRocketDocument documentCopy = new OpenRocketDocument(rocketCopy);
+               documentCopy.getDefaultConfiguration().setMotorConfigurationID(configuration.getMotorConfigurationID());
+               for (Simulation s : simulations) {
+                       documentCopy.addSimulation(s.duplicateSimulation(rocketCopy));
+               }
+               return documentCopy;
+       }
+       
+       
+
        ///////  Listeners
        
        public void addDocumentChangeListener(DocumentChangeListener listener) {
index 709ce4640f9df16e835e1dd9e45c3c0804f4b09d..3c1e2c01a0157fa285a60c79761d86eb5ea293f0 100644 (file)
@@ -15,10 +15,10 @@ import net.sf.openrocket.rocketcomponent.Configuration;
 import net.sf.openrocket.rocketcomponent.Rocket;
 import net.sf.openrocket.simulation.BasicEventSimulationEngine;
 import net.sf.openrocket.simulation.FlightData;
-import net.sf.openrocket.simulation.GUISimulationConditions;
 import net.sf.openrocket.simulation.RK4SimulationStepper;
 import net.sf.openrocket.simulation.SimulationConditions;
 import net.sf.openrocket.simulation.SimulationEngine;
+import net.sf.openrocket.simulation.SimulationOptions;
 import net.sf.openrocket.simulation.SimulationStepper;
 import net.sf.openrocket.simulation.exception.SimulationException;
 import net.sf.openrocket.simulation.exception.SimulationListenerException;
@@ -67,7 +67,7 @@ public class Simulation implements ChangeSource, Cloneable {
        
        /** The conditions to use */
        // TODO: HIGH: Change to use actual conditions class??
-       private GUISimulationConditions conditions;
+       private SimulationOptions options;
        
        private ArrayList<String> simulationListeners = new ArrayList<String>();
        
@@ -83,7 +83,7 @@ public class Simulation implements ChangeSource, Cloneable {
        
 
        /** The conditions actually used in the previous simulation, or null */
-       private GUISimulationConditions simulatedConditions = null;
+       private SimulationOptions simulatedConditions = null;
        private String simulatedMotors = null;
        private FlightData simulatedData = null;
        private int simulatedRocketID = -1;
@@ -99,14 +99,14 @@ public class Simulation implements ChangeSource, Cloneable {
                this.rocket = rocket;
                this.status = Status.NOT_SIMULATED;
                
-               conditions = new GUISimulationConditions(rocket);
-               conditions.setMotorConfigurationID(
+               options = new SimulationOptions(rocket);
+               options.setMotorConfigurationID(
                                rocket.getDefaultConfiguration().getMotorConfigurationID());
-               conditions.addChangeListener(new ConditionListener());
+               options.addChangeListener(new ConditionListener());
        }
        
        
-       public Simulation(Rocket rocket, Status status, String name, GUISimulationConditions conditions,
+       public Simulation(Rocket rocket, Status status, String name, SimulationOptions options,
                        List<String> listeners, FlightData data) {
                
                if (rocket == null)
@@ -115,8 +115,8 @@ public class Simulation implements ChangeSource, Cloneable {
                        throw new IllegalArgumentException("status cannot be null");
                if (name == null)
                        throw new IllegalArgumentException("name cannot be null");
-               if (conditions == null)
-                       throw new IllegalArgumentException("conditions cannot be null");
+               if (options == null)
+                       throw new IllegalArgumentException("options cannot be null");
                
                this.rocket = rocket;
                
@@ -130,8 +130,8 @@ public class Simulation implements ChangeSource, Cloneable {
                
                this.name = name;
                
-               this.conditions = conditions;
-               conditions.addChangeListener(new ConditionListener());
+               this.options = options;
+               options.addChangeListener(new ConditionListener());
                
                if (listeners != null) {
                        this.simulationListeners.addAll(listeners);
@@ -141,7 +141,7 @@ public class Simulation implements ChangeSource, Cloneable {
                if (data != null && this.status != Status.NOT_SIMULATED) {
                        simulatedData = data;
                        if (this.status == Status.LOADED) {
-                               simulatedConditions = conditions.clone();
+                               simulatedConditions = options.clone();
                                simulatedRocketID = rocket.getModID();
                        }
                }
@@ -169,21 +169,21 @@ public class Simulation implements ChangeSource, Cloneable {
        public Configuration getConfiguration() {
                mutex.verify();
                Configuration c = new Configuration(rocket);
-               c.setMotorConfigurationID(conditions.getMotorConfigurationID());
+               c.setMotorConfigurationID(options.getMotorConfigurationID());
                c.setAllStages();
                return c;
        }
        
        /**
-        * Returns the simulation conditions attached to this simulation.  The conditions
+        * Returns the simulation options attached to this simulation.  The options
         * may be modified freely, and the status of the simulation will change to reflect
         * the changes.
         * 
         * @return the simulation conditions.
         */
-       public GUISimulationConditions getConditions() {
+       public SimulationOptions getOptions() {
                mutex.verify();
-               return conditions;
+               return options;
        }
        
        
@@ -245,7 +245,7 @@ public class Simulation implements ChangeSource, Cloneable {
                
                if (status == Status.UPTODATE || status == Status.LOADED) {
                        if (rocket.getFunctionalModID() != simulatedRocketID ||
-                                       !conditions.equals(simulatedConditions))
+                                       !options.equals(simulatedConditions))
                                return Status.OUTDATED;
                }
                
@@ -277,11 +277,9 @@ public class Simulation implements ChangeSource, Cloneable {
                                throw new IllegalStateException("Cannot instantiate simulator.", e);
                        } catch (IllegalAccessException e) {
                                throw new IllegalStateException("Cannot access simulator instance?! BUG!", e);
-                       } catch (NullPointerException e) {
-                               throw new IllegalStateException("Simulator null", e);
                        }
                        
-                       SimulationConditions simulationConditions = conditions.toSimulationConditions();
+                       SimulationConditions simulationConditions = options.toSimulationConditions();
                        for (SimulationListener l : additionalListeners) {
                                simulationConditions.getSimulationListenerList().add(l);
                        }
@@ -306,7 +304,7 @@ public class Simulation implements ChangeSource, Cloneable {
                        log.debug("Simulation: returning from simulator, simulation took " + (t2 - t1) + "ms");
                        
                        // Set simulated info after simulation, will not be set in case of exception
-                       simulatedConditions = conditions.clone();
+                       simulatedConditions = options.clone();
                        simulatedMotors = getConfiguration().getMotorConfigurationDescription();
                        simulatedRocketID = rocket.getFunctionalModID();
                        
@@ -324,7 +322,7 @@ public class Simulation implements ChangeSource, Cloneable {
         * 
         * @return      the conditions used in the previous simulation, or <code>null</code>.
         */
-       public GUISimulationConditions getSimulatedConditions() {
+       public SimulationOptions getSimulatedConditions() {
                mutex.verify();
                return simulatedConditions;
        }
@@ -373,6 +371,7 @@ public class Simulation implements ChangeSource, Cloneable {
 
        /**
         * Returns a copy of this simulation suitable for cut/copy/paste operations.  
+        * The rocket refers to the same instance as the original simulation.
         * This excludes any simulated data.
         *  
         * @return      a copy of this simulation and its conditions.
@@ -385,7 +384,7 @@ public class Simulation implements ChangeSource, Cloneable {
                        
                        copy.mutex = SafetyMutex.newInstance();
                        copy.status = Status.NOT_SIMULATED;
-                       copy.conditions = this.conditions.clone();
+                       copy.options = this.options.clone();
                        copy.simulationListeners = this.simulationListeners.clone();
                        copy.listeners = new ArrayList<ChangeListener>();
                        copy.simulatedConditions = null;
@@ -416,7 +415,7 @@ public class Simulation implements ChangeSource, Cloneable {
                        Simulation copy = new Simulation(newRocket);
                        
                        copy.name = this.name;
-                       copy.conditions.copyFrom(this.conditions);
+                       copy.options.copyFrom(this.options);
                        copy.simulationListeners = this.simulationListeners.clone();
                        copy.simulationStepperClass = this.simulationStepperClass;
                        copy.aerodynamicCalculatorClass = this.aerodynamicCalculatorClass;
index d6cf61607c0dff5a4ed676074d443821ec0efd55..7719e0e2c4fbed9fe14d67430aea934464435426 100644 (file)
@@ -69,7 +69,7 @@ import net.sf.openrocket.simulation.FlightDataBranch;
 import net.sf.openrocket.simulation.FlightDataType;
 import net.sf.openrocket.simulation.FlightEvent;
 import net.sf.openrocket.simulation.FlightEvent.Type;
-import net.sf.openrocket.simulation.GUISimulationConditions;
+import net.sf.openrocket.simulation.SimulationOptions;
 import net.sf.openrocket.startup.Application;
 import net.sf.openrocket.unit.UnitGroup;
 import net.sf.openrocket.util.BugException;
@@ -1257,12 +1257,12 @@ class SingleSimulationHandler extends ElementHandler {
                        status = Simulation.Status.OUTDATED;
                }
                
-               GUISimulationConditions conditions;
+               SimulationOptions conditions;
                if (conditionHandler != null) {
                        conditions = conditionHandler.getConditions();
                } else {
                        warnings.add("Simulation conditions not defined, using defaults.");
-                       conditions = new GUISimulationConditions(doc.getRocket());
+                       conditions = new SimulationOptions(doc.getRocket());
                }
                
                if (name == null)
@@ -1284,14 +1284,14 @@ class SingleSimulationHandler extends ElementHandler {
 
 
 class SimulationConditionsHandler extends ElementHandler {
-       private GUISimulationConditions conditions;
+       private SimulationOptions conditions;
        private AtmosphereHandler atmosphereHandler;
        
        public SimulationConditionsHandler(Rocket rocket) {
-               conditions = new GUISimulationConditions(rocket);
+               conditions = new SimulationOptions(rocket);
        }
        
-       public GUISimulationConditions getConditions() {
+       public SimulationOptions getConditions() {
                return conditions;
        }
        
@@ -1418,7 +1418,7 @@ class AtmosphereHandler extends ElementHandler {
        }
        
        
-       public void storeSettings(GUISimulationConditions cond, WarningSet warnings) {
+       public void storeSettings(SimulationOptions cond, WarningSet warnings) {
                if (!Double.isNaN(pressure)) {
                        cond.setLaunchPressure(pressure);
                }
index 85085259809919d9d4f4d92e102cd8c131b07f67..bf23728448922b375910e2b25ee1ac37b64acdc3 100644 (file)
@@ -25,7 +25,7 @@ import net.sf.openrocket.simulation.FlightData;
 import net.sf.openrocket.simulation.FlightDataBranch;
 import net.sf.openrocket.simulation.FlightDataType;
 import net.sf.openrocket.simulation.FlightEvent;
-import net.sf.openrocket.simulation.GUISimulationConditions;
+import net.sf.openrocket.simulation.SimulationOptions;
 import net.sf.openrocket.startup.Application;
 import net.sf.openrocket.util.BugException;
 import net.sf.openrocket.util.MathUtil;
@@ -291,7 +291,7 @@ public class OpenRocketSaver extends RocketSaver {
        
        
        private void saveSimulation(Simulation simulation, double timeSkip) throws IOException {
-               GUISimulationConditions cond = simulation.getConditions();
+               SimulationOptions cond = simulation.getOptions();
                
                writeln("<simulation status=\"" + enumToXMLName(simulation.getStatus()) + "\">");
                indent++;
index a88f4b892d8a56aadbb779781ff41b60710e114f..4e877affe84c8454cfc68da3b6cc17a6f9102f97 100644 (file)
@@ -37,17 +37,21 @@ import net.sf.openrocket.util.Reflection;
  * 
  * @author Sampo Niskanen <sampo.niskanen@iki.fi>
  */
-
 public class BooleanModel extends AbstractAction implements ChangeListener, Invalidatable {
        private static final LogHelper log = Application.getLogger();
        
        private final ChangeSource source;
        private final String valueName;
        
+       /* Only used when referencing a ChangeSource! */
        private final Method getMethod;
        private final Method setMethod;
        private final Method getEnabled;
        
+       /* Only used with internal boolean value! */
+       private boolean value;
+       
+
        private final List<Component> components = new ArrayList<Component>();
        private final List<Boolean> componentEnableState = new ArrayList<Boolean>();
        
@@ -61,6 +65,34 @@ public class BooleanModel extends AbstractAction implements ChangeListener, Inva
        private Invalidator invalidator = new Invalidator(this);
        
        
+       /**
+        * Construct a BooleanModel that holds the boolean value within itself.
+        * 
+        * @param initialValue  the initial value of the boolean
+        */
+       public BooleanModel(boolean initialValue) {
+               this.valueName = null;
+               this.source = null;
+               this.getMethod = null;
+               this.setMethod = null;
+               this.getEnabled = null;
+               
+               this.value = initialValue;
+               
+               oldValue = getValue();
+               oldEnabled = getIsEnabled();
+               
+               this.setEnabled(oldEnabled);
+               this.putValue(SELECTED_KEY, oldValue);
+               
+       }
+       
+       /**
+        * Construct a BooleanModel that references the boolean from a ChangeSource method.
+        * 
+        * @param source                the boolean source.
+        * @param valueName             the name of the getter/setter method (without the get/is/set prefix)
+        */
        public BooleanModel(ChangeSource source, String valueName) {
                this.source = source;
                this.valueName = valueName;
@@ -109,24 +141,41 @@ public class BooleanModel extends AbstractAction implements ChangeListener, Inva
        }
        
        public boolean getValue() {
-               try {
-                       return (Boolean) getMethod.invoke(source);
-               } catch (IllegalAccessException e) {
-                       throw new BugException("getMethod execution error for source " + source, e);
-               } catch (InvocationTargetException e) {
-                       throw Reflection.handleWrappedException(e);
+               
+               if (getMethod != null) {
+                       
+                       try {
+                               return (Boolean) getMethod.invoke(source);
+                       } catch (IllegalAccessException e) {
+                               throw new BugException("getMethod execution error for source " + source, e);
+                       } catch (InvocationTargetException e) {
+                               throw Reflection.handleWrappedException(e);
+                       }
+                       
+               } else {
+                       
+                       // Use internal value
+                       return value;
+                       
                }
        }
        
        public void setValue(boolean b) {
                checkState(true);
                log.debug("Setting value of " + this + " to " + b);
-               try {
-                       setMethod.invoke(source, new Object[] { b });
-               } catch (IllegalAccessException e) {
-                       throw new BugException("setMethod execution error for source " + source, e);
-               } catch (InvocationTargetException e) {
-                       throw Reflection.handleWrappedException(e);
+               
+               if (setMethod != null) {
+                       try {
+                               setMethod.invoke(source, new Object[] { b });
+                       } catch (IllegalAccessException e) {
+                               throw new BugException("setMethod execution error for source " + source, e);
+                       } catch (InvocationTargetException e) {
+                               throw Reflection.handleWrappedException(e);
+                       }
+               } else {
+                       // Manually fire state change - normally the ChangeSource fires it
+                       value = b;
+                       stateChanged(null);
                }
        }
        
@@ -271,7 +320,11 @@ public class BooleanModel extends AbstractAction implements ChangeListener, Inva
        @Override
        public String toString() {
                if (toString == null) {
-                       toString = "BooleanModel[" + source.getClass().getSimpleName() + ":" + valueName + "]";
+                       if (source != null) {
+                               toString = "BooleanModel[" + source.getClass().getSimpleName() + ":" + valueName + "]";
+                       } else {
+                               toString = "BooleanModel[internal value]";
+                       }
                }
                return toString;
        }
index ce81e1076ff4b3f31b8656cbdf7a25d200fb1181..e1fc3071c939c4a5308b92f31d1e63d7563ba379 100644 (file)
@@ -261,7 +261,7 @@ public class DoubleModel implements ChangeListener, ChangeSource, Invalidatable
                                // Use quadratic scale
                                // Further solution of the quadratic equation
                                //   a*x^2 + b*x + c-value == 0
-                               x = (Math.sqrt(quad1 * quad1 - 4 * quad2 * (quad0 - value)) - quad1) / (2 * quad2);
+                               x = (MathUtil.safeSqrt(quad1 * quad1 - 4 * quad2 * (quad0 - value)) - quad1) / (2 * quad2);
                        }
                        return (int) (x * MAX);
                }
diff --git a/src/net/sf/openrocket/gui/components/CsvOptionPanel.java b/src/net/sf/openrocket/gui/components/CsvOptionPanel.java
new file mode 100644 (file)
index 0000000..b856e87
--- /dev/null
@@ -0,0 +1,126 @@
+package net.sf.openrocket.gui.components;
+
+import javax.swing.BorderFactory;
+import javax.swing.JCheckBox;
+import javax.swing.JComboBox;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+
+import net.miginfocom.swing.MigLayout;
+import net.sf.openrocket.l10n.Translator;
+import net.sf.openrocket.startup.Application;
+import net.sf.openrocket.util.Prefs;
+
+/**
+ * A panel that shows options for saving CSV files.
+ * 
+ * @author Sampo Niskanen <sampo.niskanen@iki.fi>
+ */
+public class CsvOptionPanel extends JPanel {
+       
+       private static final Translator trans = Application.getTranslator();
+       
+       private static final String SPACE = trans.get("CsvOptionPanel.separator.space");
+       private static final String TAB = trans.get("CsvOptionPanel.separator.tab");
+       
+       private final String baseClassName;
+       
+       private final JComboBox fieldSeparator;
+       private final JCheckBox[] options;
+       private final JComboBox commentCharacter;
+       
+       /**
+        * Sole constructor.
+        * 
+        * @param includeComments       a list of comment inclusion options to provide;
+        *                                                      every second item is the option name and every second the tooltip
+        */
+       public CsvOptionPanel(Class<?> baseClass, String... includeComments) {
+               super(new MigLayout("fill, insets 0"));
+               
+               this.baseClassName = baseClass.getSimpleName();
+               
+               JPanel panel;
+               JLabel label;
+               String tip;
+               
+
+               // TODO: HIGH: Rename the translation keys
+               
+               // Field separator panel
+               panel = new JPanel(new MigLayout("fill"));
+               panel.setBorder(BorderFactory.createTitledBorder(trans.get("SimExpPan.border.Fieldsep")));
+               
+               label = new JLabel(trans.get("SimExpPan.lbl.Fieldsepstr"));
+               tip = trans.get("SimExpPan.lbl.longA1") +
+                               trans.get("SimExpPan.lbl.longA2");
+               label.setToolTipText(tip);
+               panel.add(label, "gapright unrel");
+               
+               fieldSeparator = new JComboBox(new String[] { ",", ";", SPACE, TAB });
+               fieldSeparator.setEditable(true);
+               fieldSeparator.setSelectedItem(Prefs.getString(Prefs.EXPORT_FIELD_SEPARATOR, ","));
+               fieldSeparator.setToolTipText(tip);
+               panel.add(fieldSeparator, "growx");
+               
+               this.add(panel, "growx, wrap unrel");
+               
+
+
+               // Comments separator panel
+               panel = new JPanel(new MigLayout("fill"));
+               panel.setBorder(BorderFactory.createTitledBorder(trans.get("SimExpPan.border.Comments")));
+               
+
+               // List of include comments options
+               if (includeComments.length % 2 == 1) {
+                       throw new IllegalArgumentException("Invalid argument length, must be even, length=" + includeComments.length);
+               }
+               options = new JCheckBox[includeComments.length / 2];
+               for (int i = 0; i < includeComments.length / 2; i++) {
+                       options[i] = new JCheckBox(includeComments[i * 2]);
+                       options[i].setToolTipText(includeComments[i * 2 + 1]);
+                       options[i].setSelected(Prefs.getBoolean("csvOptions." + baseClassName + "." + i, true));
+                       panel.add(options[i], "wrap");
+               }
+               
+
+               label = new JLabel(trans.get("SimExpPan.lbl.Commentchar"));
+               tip = trans.get("SimExpPan.lbl.ttip.Commentchar");
+               label.setToolTipText(tip);
+               panel.add(label, "split 2, gapright unrel");
+               
+               commentCharacter = new JComboBox(new String[] { "#", "%", ";" });
+               commentCharacter.setEditable(true);
+               commentCharacter.setSelectedItem(Prefs.getString(Prefs.EXPORT_COMMENT_CHARACTER, "#"));
+               commentCharacter.setToolTipText(tip);
+               panel.add(commentCharacter, "growx");
+               
+               this.add(panel, "growx, wrap");
+       }
+       
+       
+       public String getFieldSeparator() {
+               return fieldSeparator.getSelectedItem().toString();
+       }
+       
+       public String getCommentCharacter() {
+               return commentCharacter.getSelectedItem().toString();
+       }
+       
+       public boolean getSelectionOption(int index) {
+               return options[index].isSelected();
+       }
+       
+       /**
+        * Store the selected options to the user preferences.
+        */
+       public void storePreferences() {
+               Prefs.putString(Prefs.EXPORT_FIELD_SEPARATOR, getFieldSeparator());
+               Prefs.putString(Prefs.EXPORT_COMMENT_CHARACTER, getCommentCharacter());
+               for (int i = 0; i < options.length; i++) {
+                       Prefs.putBoolean("csvOptions." + baseClassName + "." + i, options[i].isSelected());
+               }
+       }
+       
+}
index 616b3adcdc91fa41954ba8dd0794780f74d1202e..1e37bb6f2a7c8f961a1f1014838411fa3df6eff9 100644 (file)
@@ -16,14 +16,33 @@ public class DescriptionArea extends JScrollPane {
        private final JEditorPane editorPane;
        
        
+       /**
+        * Construct a description area with the specified number of rows, default description font size,
+        * being opaque.
+        * 
+        * @param rows  the number of rows
+        */
        public DescriptionArea(int rows) {
                this("", rows, -1);
        }
        
+       /**
+        * Construct a description area with the specified number of rows and size, being opaque.
+        * 
+        * @param rows  the number of rows.
+        * @param size  the font size difference compared to the default font size.
+        */
        public DescriptionArea(int rows, float size) {
                this("", rows, size);
        }
        
+       /**
+        * Construct an opaque description area with the specified number of rows, size and text, being opaque.
+        * 
+        * @param text  the initial text.
+        * @param rows  the number of rows.
+        * @param size  the font size difference compared to the default font size.
+        */
        public DescriptionArea(String text, int rows, float size) {
                this(text, rows, size, true);
        }
diff --git a/src/net/sf/openrocket/gui/components/DoubleCellEditor.java b/src/net/sf/openrocket/gui/components/DoubleCellEditor.java
new file mode 100644 (file)
index 0000000..6ff8682
--- /dev/null
@@ -0,0 +1,42 @@
+package net.sf.openrocket.gui.components;
+
+import java.awt.Component;
+
+import javax.swing.AbstractCellEditor;
+import javax.swing.JSpinner;
+import javax.swing.JTable;
+import javax.swing.table.TableCellEditor;
+
+import net.sf.openrocket.gui.SpinnerEditor;
+import net.sf.openrocket.gui.adaptors.DoubleModel;
+
+public class DoubleCellEditor extends AbstractCellEditor implements TableCellEditor {
+       
+       private final JSpinner editor;
+       private final DoubleModel model;
+       
+       public DoubleCellEditor() {
+               model = new DoubleModel(0);
+               editor = new JSpinner(model.getSpinnerModel());
+               editor.setEditor(new SpinnerEditor(editor));
+               //              editor.addChangeListener(this);
+       }
+       
+       
+       @Override
+       public Component getTableCellEditorComponent(JTable table, Object value,
+                       boolean isSelected, int row, int column) {
+               
+               double val = (Double) value;
+               model.setValue(val);
+               
+               return editor;
+       }
+       
+       
+       @Override
+       public Object getCellEditorValue() {
+               return model.getValue();
+       }
+       
+}
index 62f79d6d3dcc79f4d09c925fe992310c86b6331f..cd73c5f9e1d55a37768f3a266b90cdacc2c720f0 100644 (file)
@@ -8,16 +8,12 @@ import java.util.Arrays;
 
 import javax.swing.BorderFactory;
 import javax.swing.JButton;
-import javax.swing.JCheckBox;
-import javax.swing.JComboBox;
 import javax.swing.JFileChooser;
 import javax.swing.JLabel;
-import javax.swing.JOptionPane;
 import javax.swing.JPanel;
 import javax.swing.JScrollPane;
 import javax.swing.JTable;
 import javax.swing.SwingUtilities;
-import javax.swing.filechooser.FileFilter;
 import javax.swing.table.AbstractTableModel;
 import javax.swing.table.TableCellRenderer;
 import javax.swing.table.TableColumn;
@@ -32,32 +28,20 @@ import net.sf.openrocket.simulation.FlightDataType;
 import net.sf.openrocket.startup.Application;
 import net.sf.openrocket.unit.Unit;
 import net.sf.openrocket.unit.UnitGroup;
+import net.sf.openrocket.util.FileHelper;
 import net.sf.openrocket.util.GUIUtil;
 import net.sf.openrocket.util.Prefs;
 import net.sf.openrocket.util.SaveCSVWorker;
 
 public class SimulationExportPanel extends JPanel {
-
+       
        private static final String SPACE = "SPACE";
        private static final String TAB = "TAB";
        private static final Translator trans = Application.getTranslator();
        
-       private static final FileFilter CSV_FILE_FILTER = new FileFilter() {
-               @Override
-               public String getDescription() {
-                       //// Comma Separated Files (*.csv)
-                       return trans.get("SimExpPan.desc");
-               }
-               @Override
-               public boolean accept(File f) {
-                       if (f.isDirectory())
-                               return true;
-                       String name = f.getName().toLowerCase();
-                       return name.endsWith(".csv");
-               }
-    };
-
-       
+       private static final int OPTION_SIMULATION_COMMENTS = 0;
+       private static final int OPTION_FIELD_DESCRIPTIONS = 1;
+       private static final int OPTION_FLIGHT_EVENTS = 2;
        
        private final JTable table;
        private final SelectionTableModel tableModel;
@@ -70,38 +54,32 @@ public class SimulationExportPanel extends JPanel {
        private final FlightDataType[] types;
        private final Unit[] units;
        
-       private final JComboBox fieldSeparator;
-       private final JCheckBox simulationComments;
-       private final JCheckBox fieldNameComments;
-       private final JCheckBox eventComments;
-       private final JComboBox commentCharacter;
+       private final CsvOptionPanel csvOptions;
        
        
        public SimulationExportPanel(Simulation sim) {
                super(new MigLayout("fill, flowy"));
-
-               JLabel label;
+               
                JPanel panel;
                JButton button;
-               String tip;
-               
                
+
                this.simulation = sim;
                
                // TODO: MEDIUM: Only exports primary branch
                
                final FlightData data = simulation.getSimulatedData();
-
+               
                // Check that data exists
-               if (data == null  || data.getBranchCount() == 0 ||
+               if (data == null || data.getBranchCount() == 0 ||
                                data.getBranch(0).getTypes().length == 0) {
                        throw new IllegalArgumentException("No data for panel");
                }
                
-               
+
                // Create the data model
                branch = data.getBranch(0);
-
+               
                types = branch.getTypes();
                Arrays.sort(types);
                
@@ -112,16 +90,16 @@ public class SimulationExportPanel extends JPanel {
                        units[i] = types[i].getUnitGroup().getDefaultUnit();
                }
                
-               
+
                //// Create the panel
                
-               
+
                // Set up the variable selection table
                tableModel = new SelectionTableModel();
                table = new JTable(tableModel);
-               table.setDefaultRenderer(Object.class, 
+               table.setDefaultRenderer(Object.class,
                                new SelectionBackgroundCellRenderer(table.getDefaultRenderer(Object.class)));
-               table.setDefaultRenderer(Boolean.class, 
+               table.setDefaultRenderer(Boolean.class,
                                new SelectionBackgroundCellRenderer(table.getDefaultRenderer(Boolean.class)));
                table.setRowSelectionAllowed(false);
                table.setColumnSelectionAllowed(false);
@@ -146,7 +124,7 @@ public class SimulationExportPanel extends JPanel {
                
                col = columnModel.getColumn(2);
                col.setPreferredWidth(100);
-
+               
                table.addMouseListener(new GUIUtil.BooleanTableClickListener(table));
                
                // Add table
@@ -174,85 +152,31 @@ public class SimulationExportPanel extends JPanel {
                });
                panel.add(button, "growx 1, sizegroup selectbutton, wrap");
                
-               
+
                selectedCountLabel = new JLabel();
                updateSelectedCount();
                panel.add(selectedCountLabel);
                
                this.add(panel, "grow 100, wrap");
                
+
+               // These need to be in the order of the OPTIONS_XXX indices
+               csvOptions = new CsvOptionPanel(SimulationExportPanel.class,
+                               trans.get("SimExpPan.checkbox.Includesimudesc"),
+                               trans.get("SimExpPan.checkbox.ttip.Includesimudesc"),
+                               trans.get("SimExpPan.checkbox.Includefielddesc"),
+                               trans.get("SimExpPan.checkbox.ttip.Includefielddesc"),
+                               trans.get("SimExpPan.checkbox.Incflightevents"),
+                               trans.get("SimExpPan.checkbox.ttip.Incflightevents"));
                
+               this.add(csvOptions, "spany, split, growx 1");
                
-               // Field separator panel
-               panel = new JPanel(new MigLayout("fill"));
-               panel.setBorder(BorderFactory.createTitledBorder(trans.get("SimExpPan.border.Fieldsep")));
-               
-               label = new JLabel(trans.get("SimExpPan.lbl.Fieldsepstr"));
-               //// <html>The string used to separate the fields in the exported file.<br>
-               //// Use ',' for a Comma Separated Values (CSV) file.
-               tip = trans.get("SimExpPan.lbl.longA1") +
-               trans.get("SimExpPan.lbl.longA2");
-               label.setToolTipText(tip);
-               panel.add(label);
-               
-               fieldSeparator = new JComboBox(new String[] { ",", ";", SPACE, TAB });
-               fieldSeparator.setEditable(true);
-               fieldSeparator.setSelectedItem(Prefs.getString(Prefs.EXPORT_FIELD_SEPARATOR, 
-                               ","));
-               fieldSeparator.setToolTipText(tip);
-               panel.add(fieldSeparator);
-               
-               this.add(panel, "spany, split, growx 1");
-               
-               
-               
-               
-               // Comments separator panel
-               panel = new JPanel(new MigLayout("fill"));
-               //// Comments
-               panel.setBorder(BorderFactory.createTitledBorder(trans.get("SimExpPan.border.Comments")));
-               
-               //// Include simulation description
-               simulationComments = new JCheckBox(trans.get("SimExpPan.checkbox.Includesimudesc"));
-               //// Include a comment at the beginning of the file describing the simulation.
-               simulationComments.setToolTipText(trans.get("SimExpPan.checkbox.ttip.Includesimudesc"));
-               simulationComments.setSelected(Prefs.getBoolean(Prefs.EXPORT_SIMULATION_COMMENT, 
-                               true));
-               panel.add(simulationComments, "wrap");
-               
-               //// Include field descriptions
-               fieldNameComments = new JCheckBox(trans.get("SimExpPan.checkbox.Includefielddesc"));
-               //// Include a comment line with the descriptions of the exported variables.
-               fieldNameComments.setToolTipText(trans.get("SimExpPan.checkbox.ttip.Includefielddesc"));
-               fieldNameComments.setSelected(Prefs.getBoolean(Prefs.EXPORT_FIELD_NAME_COMMENT, true));
-               panel.add(fieldNameComments, "wrap");
-               
-               
-               eventComments = new JCheckBox(trans.get("SimExpPan.checkbox.Incflightevents"));
-               eventComments.setToolTipText(trans.get("SimExpPan.checkbox.ttip.Incflightevents"));
-               eventComments.setSelected(Prefs.getBoolean(Prefs.EXPORT_EVENT_COMMENTS, true));
-               panel.add(eventComments, "wrap");
-               
-               
-               label = new JLabel(trans.get("SimExpPan.lbl.Commentchar"));
-               tip = trans.get("SimExpPan.lbl.ttip.Commentchar");
-               label.setToolTipText(tip);
-               panel.add(label, "split 2");
-               
-               commentCharacter = new JComboBox(new String[] { "#", "%", ";" });
-               commentCharacter.setEditable(true);
-               commentCharacter.setSelectedItem(Prefs.getString(Prefs.EXPORT_COMMENT_CHARACTER, "#"));
-               commentCharacter.setToolTipText(tip);
-               panel.add(commentCharacter);
-               
-               this.add(panel, "growx 1");
 
-               
                // Space-filling panel
                panel = new JPanel();
                this.add(panel, "width 1, height 1, grow 1");
                
-               
+
                // Export button
                button = new JButton(trans.get("SimExpPan.but.Exporttofile"));
                button.addActionListener(new ActionListener() {
@@ -268,7 +192,7 @@ public class SimulationExportPanel extends JPanel {
        
        private void doExport() {
                JFileChooser chooser = new JFileChooser();
-               chooser.setFileFilter(CSV_FILE_FILTER);
+               chooser.setFileFilter(FileHelper.CSV_FILE_FILTER);
                chooser.setCurrentDirectory(Prefs.getDefaultDirectory());
                
                if (chooser.showSaveDialog(this) != JFileChooser.APPROVE_OPTION)
@@ -278,49 +202,33 @@ public class SimulationExportPanel extends JPanel {
                if (file == null)
                        return;
                
-               if (file.getName().indexOf('.') < 0) {
-                       String name = file.getAbsolutePath();
-                       name = name + ".csv";
-                       file = new File(name);
-               }
-
-               if (file.exists()) {
-                       int ret = JOptionPane.showConfirmDialog(this,
-                                       //// File 
-                                       trans.get("SimExpPan.Fileexists.desc1") + file.getName() + 
-                                       //// \" exists.  Overwrite?
-                                       trans.get("SimExpPan.Fileexists.desc2"), 
-                                       //// File exists
-                                       trans.get("SimExpPan.Fileexists.title"), JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE);
-                       if (ret != JOptionPane.YES_OPTION)
-                               return;
+               file = FileHelper.ensureExtension(file, "csv");
+               if (!FileHelper.confirmWrite(file, this)) {
+                       return;
                }
+               
 
-               String commentChar = commentCharacter.getSelectedItem().toString();
-               String fieldSep = fieldSeparator.getSelectedItem().toString();
-               boolean simulationComment = simulationComments.isSelected();
-               boolean fieldComment = fieldNameComments.isSelected();
-               boolean eventComment = eventComments.isSelected();
+               String commentChar = csvOptions.getCommentCharacter();
+               String fieldSep = csvOptions.getFieldSeparator();
+               boolean simulationComment = csvOptions.getSelectionOption(OPTION_SIMULATION_COMMENTS);
+               boolean fieldComment = csvOptions.getSelectionOption(OPTION_FIELD_DESCRIPTIONS);
+               boolean eventComment = csvOptions.getSelectionOption(OPTION_FLIGHT_EVENTS);
+               csvOptions.storePreferences();
                
                // Store preferences and export
                int n = 0;
                Prefs.setDefaultDirectory(chooser.getCurrentDirectory());
-               for (int i=0; i < selected.length; i++) {
+               for (int i = 0; i < selected.length; i++) {
                        Prefs.setExportSelected(types[i], selected[i]);
                        if (selected[i])
                                n++;
                }
-               Prefs.putString(Prefs.EXPORT_FIELD_SEPARATOR, fieldSep);
-               Prefs.putString(Prefs.EXPORT_COMMENT_CHARACTER, commentChar);
-               Prefs.putBoolean(Prefs.EXPORT_EVENT_COMMENTS, eventComment);
-               Prefs.putBoolean(Prefs.EXPORT_FIELD_NAME_COMMENT, fieldComment);
-               Prefs.putBoolean(Prefs.EXPORT_SIMULATION_COMMENT, simulationComment);
-               
                
+
                FlightDataType[] fieldTypes = new FlightDataType[n];
                Unit[] fieldUnits = new Unit[n];
                int pos = 0;
-               for (int i=0; i < selected.length; i++) {
+               for (int i = 0; i < selected.length; i++) {
                        if (selected[i]) {
                                fieldTypes[pos] = types[i];
                                fieldUnits[pos] = units[i];
@@ -334,9 +242,9 @@ public class SimulationExportPanel extends JPanel {
                        fieldSep = "\t";
                }
                
-               
-               SaveCSVWorker.export(file, simulation, branch, fieldTypes, fieldUnits, fieldSep, 
-                               commentChar, simulationComment, fieldComment, eventComment, 
+
+               SaveCSVWorker.export(file, simulation, branch, fieldTypes, fieldUnits, fieldSep,
+                               commentChar, simulationComment, fieldComment, eventComment,
                                SwingUtilities.getWindowAncestor(this));
        }
        
@@ -346,7 +254,7 @@ public class SimulationExportPanel extends JPanel {
                int n = 0;
                String str;
                
-               for (int i=0; i < selected.length; i++) {
+               for (int i = 0; i < selected.length; i++) {
                        if (selected[i])
                                n++;
                }
@@ -357,21 +265,21 @@ public class SimulationExportPanel extends JPanel {
                } else {
                        //// Exporting 
                        //// variables out of
-                       str = trans.get("SimExpPan.ExportingVar.desc2") + " "+n+" " + 
-                       trans.get("SimExpPan.ExportingVar.desc3") + " " + total + ".";
+                       str = trans.get("SimExpPan.ExportingVar.desc2") + " " + n + " " +
+                                       trans.get("SimExpPan.ExportingVar.desc3") + " " + total + ".";
                }
-
+               
                selectedCountLabel.setText(str);
        }
        
        
-       
+
        /**
         * A table cell renderer that uses another renderer and sets the background and
         * foreground of the returned component based on the selection of the variable.
         */
        private class SelectionBackgroundCellRenderer implements TableCellRenderer {
-
+               
                private final TableCellRenderer renderer;
                
                public SelectionBackgroundCellRenderer(TableCellRenderer renderer) {
@@ -382,7 +290,7 @@ public class SimulationExportPanel extends JPanel {
                public Component getTableCellRendererComponent(JTable table, Object value,
                                boolean isSelected, boolean hasFocus, int row, int column) {
                        
-                       Component component = renderer.getTableCellRendererComponent(table, 
+                       Component component = renderer.getTableCellRendererComponent(table,
                                        value, isSelected, hasFocus, row, column);
                        
                        if (selected[row]) {
@@ -406,12 +314,12 @@ public class SimulationExportPanel extends JPanel {
                private static final int SELECTED = 0;
                private static final int NAME = 1;
                private static final int UNIT = 2;
-
+               
                @Override
                public int getColumnCount() {
                        return 3;
                }
-
+               
                @Override
                public int getRowCount() {
                        return types.length;
@@ -447,10 +355,10 @@ public class SimulationExportPanel extends JPanel {
                                throw new IndexOutOfBoundsException("column=" + column);
                        }
                }
-
+               
                @Override
                public Object getValueAt(int row, int column) {
-
+                       
                        switch (column) {
                        case SELECTED:
                                return selected[row];
@@ -462,34 +370,34 @@ public class SimulationExportPanel extends JPanel {
                                return units[row];
                                
                        default:
-                               throw new IndexOutOfBoundsException("column="+column);
+                               throw new IndexOutOfBoundsException("column=" + column);
                        }
                        
                }
-
+               
                @Override
                public void setValueAt(Object value, int row, int column) {
                        
                        switch (column) {
                        case SELECTED:
-                               selected[row] = (Boolean)value;
+                               selected[row] = (Boolean) value;
                                this.fireTableRowsUpdated(row, row);
                                updateSelectedCount();
                                break;
-                               
+                       
                        case NAME:
                                break;
-                               
+                       
                        case UNIT:
-                               units[row] = (Unit)value;
+                               units[row] = (Unit) value;
                                break;
-                               
+                       
                        default:
-                               throw new IndexOutOfBoundsException("column="+column);
+                               throw new IndexOutOfBoundsException("column=" + column);
                        }
                        
                }
-
+               
                @Override
                public boolean isCellEditable(int row, int column) {
                        switch (column) {
@@ -503,7 +411,7 @@ public class SimulationExportPanel extends JPanel {
                                return types[row].getUnitGroup().getUnitCount() > 1;
                                
                        default:
-                               throw new IndexOutOfBoundsException("column="+column);
+                               throw new IndexOutOfBoundsException("column=" + column);
                        }
                }
                
index 7afa28ab6d39076f604fdd829e8209fc0b13225a..2ff47abc028f02de1391fb378a1547f8dd94cb07 100644 (file)
@@ -20,11 +20,10 @@ import net.sf.openrocket.unit.UnitGroup;
  * 
  * @author Sampo Niskanen <sampo.niskanen@iki.fi>
  */
-public abstract class UnitCellEditor extends AbstractCellEditor 
-implements TableCellEditor, ActionListener {
-
-       private final JComboBox editor;
+public abstract class UnitCellEditor extends AbstractCellEditor
+               implements TableCellEditor, ActionListener {
        
+       private final JComboBox editor;
        
        public UnitCellEditor() {
                editor = new JComboBox();
@@ -36,12 +35,12 @@ implements TableCellEditor, ActionListener {
        @Override
        public Component getTableCellEditorComponent(JTable table, Object value,
                        boolean isSelected, int row, int column) {
-
+               
                Unit unit = (Unit) value;
                UnitGroup group = getUnitGroup(unit, row, column);
                
                editor.removeAllItems();
-               for (Unit u: group.getUnits()) {
+               for (Unit u : group.getUnits()) {
                        editor.addItem(u);
                }
                
@@ -49,22 +48,22 @@ implements TableCellEditor, ActionListener {
                
                return editor;
        }
-
+       
        
        @Override
        public Object getCellEditorValue() {
                return editor.getSelectedItem();
        }
        
-
        
+
        @Override
        public void actionPerformed(ActionEvent e) {
                // End editing when a value has been selected
                this.fireEditingStopped();
        }
-
-
+       
+       
        /**
         * Return the unit group corresponding to the specified cell.
         * 
index 0edfddae8b46109b0e32b3b4b25635a131b9f8d0..006b30f43e2729d8ac18e33eb3365e3d56dfa165 100644 (file)
@@ -1,5 +1,6 @@
 package net.sf.openrocket.gui.dialogs;
 
+import java.awt.SplashScreen;
 import java.awt.Window;
 import java.awt.event.ActionEvent;
 import java.awt.event.ActionListener;
@@ -17,10 +18,15 @@ import net.sf.openrocket.logging.LogHelper;
 import net.sf.openrocket.startup.Application;
 import net.sf.openrocket.util.GUIUtil;
 
+/**
+ * A progress dialog displayed while loading motors.
+ * 
+ * @author Sampo Niskanen <sampo.niskanen@iki.fi>
+ */
 public class MotorDatabaseLoadingDialog extends JDialog {
        private static final LogHelper log = Application.getLogger();
        private static final Translator trans = Application.getTranslator();
-
+       
        
        private MotorDatabaseLoadingDialog(Window parent) {
                //// Loading motors
@@ -44,7 +50,8 @@ public class MotorDatabaseLoadingDialog extends JDialog {
        
        /**
         * Check whether the motor database is loaded and block until it is.
-        * An uncloseable modal dialog window is opened while loading.
+        * An uncloseable modal dialog window is opened while loading unless the splash screen
+        * is still being displayed.
         * 
         * @param parent        the parent window for the dialog, or <code>null</code>
         */
@@ -53,31 +60,54 @@ public class MotorDatabaseLoadingDialog extends JDialog {
                if (db.isLoaded())
                        return;
                
-               log.info(1, "Motor database not loaded yet, displaying dialog");
-               
-               final MotorDatabaseLoadingDialog dialog = new MotorDatabaseLoadingDialog(parent);
-               
-               final Timer timer = new Timer(100, new ActionListener() {
-                       private int count = 0;
+               if (SplashScreen.getSplashScreen() == null) {
+                       
+                       log.info(1, "Motor database not loaded yet, displaying dialog");
+                       
+                       final MotorDatabaseLoadingDialog dialog = new MotorDatabaseLoadingDialog(parent);
+                       
+                       final Timer timer = new Timer(100, new ActionListener() {
+                               private int count = 0;
+                               
+                               @Override
+                               public void actionPerformed(ActionEvent e) {
+                                       count++;
+                                       if (db.isLoaded()) {
+                                               log.debug("Database loaded, closing dialog");
+                                               dialog.setVisible(false);
+                                       } else if (count % 10 == 0) {
+                                               log.debug("Database not loaded, count=" + count);
+                                       }
+                               }
+                       });
+                       
+                       db.setInUse();
+                       timer.start();
+                       dialog.setVisible(true);
+                       timer.stop();
+                       
+               } else {
+                       
+                       log.info(1, "Motor database not loaded yet, splash screen still present, delaying until loaded");
                        
-                       @Override
-                       public void actionPerformed(ActionEvent e) {
+                       db.setInUse();
+                       int count = 0;
+                       while (!db.isLoaded()) {
+                               try {
+                                       Thread.sleep(100);
+                               } catch (InterruptedException e) {
+                                       // No-op
+                               }
+                               
                                count++;
-                               if (db.isLoaded()) {
-                                       log.debug("Database loaded, closing dialog");
-                                       dialog.setVisible(false);
-                               } else if (count % 10 == 0) {
+                               if (count % 10 == 0) {
                                        log.debug("Database not loaded, count=" + count);
                                }
                        }
-               });
-               
-               db.setInUse();
-               timer.start();
-               dialog.setVisible(true);
-               timer.stop();
+                       
+               }
                
-               log.debug("Motor database now loaded");
+               log.info("Motor database now loaded");
        }
        
 }
diff --git a/src/net/sf/openrocket/gui/dialogs/optimization/FunctionEvaluationData.java b/src/net/sf/openrocket/gui/dialogs/optimization/FunctionEvaluationData.java
new file mode 100644 (file)
index 0000000..6162e96
--- /dev/null
@@ -0,0 +1,67 @@
+package net.sf.openrocket.gui.dialogs.optimization;
+
+import net.sf.openrocket.optimization.general.Point;
+import net.sf.openrocket.unit.Value;
+
+/**
+ * Value object for function evaluation information.
+ * 
+ * @author Sampo Niskanen <sampo.niskanen@iki.fi>
+ */
+public class FunctionEvaluationData {
+       
+       private final Point point;
+       private final Value[] state;
+       private final Value domainReference;
+       private final Value parameterValue;
+       private final double goalValue;
+       
+       
+       public FunctionEvaluationData(Point point, Value[] state, Value domainReference, Value parameterValue, double goalValue) {
+               this.point = point;
+               this.state = state.clone();
+               this.domainReference = domainReference;
+               this.parameterValue = parameterValue;
+               this.goalValue = goalValue;
+       }
+       
+       
+       /**
+        * Return the function evaluation point (in 0...1 range).
+        */
+       public Point getPoint() {
+               return point;
+       }
+       
+       
+       /**
+        * Return the function evaluation state in SI units + units.
+        */
+       public Value[] getState() {
+               return state;
+       }
+       
+       
+       /**
+        * Return the domain description.
+        */
+       public Value getDomainReference() {
+               return domainReference;
+       }
+       
+       
+       /**
+        * Return the optimization parameter value (or NaN is outside of domain).
+        */
+       public Value getParameterValue() {
+               return parameterValue;
+       }
+       
+       
+       /**
+        * Return the function goal value.
+        */
+       public double getGoalValue() {
+               return goalValue;
+       }
+}
index a68412e175b051cbacb85ec876adce0c1a9d35f9..fe0e37744ad17b37974d708efb2035739fe8f49e 100644 (file)
 package net.sf.openrocket.gui.dialogs.optimization;
 
+import java.awt.Component;
 import java.awt.Window;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.Writer;
+import java.text.Collator;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
 
+import javax.swing.BorderFactory;
+import javax.swing.DefaultComboBoxModel;
+import javax.swing.JButton;
+import javax.swing.JCheckBox;
+import javax.swing.JComboBox;
+import javax.swing.JComponent;
 import javax.swing.JDialog;
+import javax.swing.JFileChooser;
+import javax.swing.JLabel;
+import javax.swing.JOptionPane;
 import javax.swing.JPanel;
 import javax.swing.JScrollPane;
-import javax.swing.JTree;
+import javax.swing.JSpinner;
+import javax.swing.JTable;
+import javax.swing.JToggleButton;
+import javax.swing.ListSelectionModel;
+import javax.swing.border.TitledBorder;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+import javax.swing.event.ListSelectionEvent;
+import javax.swing.event.ListSelectionListener;
+import javax.swing.event.TreeSelectionEvent;
+import javax.swing.event.TreeSelectionListener;
+import javax.swing.table.AbstractTableModel;
+import javax.swing.table.DefaultTableCellRenderer;
+import javax.swing.table.TableColumnModel;
+import javax.swing.tree.DefaultMutableTreeNode;
+import javax.swing.tree.TreePath;
 
 import net.miginfocom.swing.MigLayout;
 import net.sf.openrocket.document.OpenRocketDocument;
+import net.sf.openrocket.document.Simulation;
+import net.sf.openrocket.gui.SpinnerEditor;
+import net.sf.openrocket.gui.adaptors.DoubleModel;
+import net.sf.openrocket.gui.components.CsvOptionPanel;
+import net.sf.openrocket.gui.components.DescriptionArea;
+import net.sf.openrocket.gui.components.DoubleCellEditor;
+import net.sf.openrocket.gui.components.StyledLabel;
+import net.sf.openrocket.gui.components.StyledLabel.Style;
+import net.sf.openrocket.gui.components.UnitCellEditor;
+import net.sf.openrocket.gui.components.UnitSelector;
+import net.sf.openrocket.gui.scalefigure.RocketFigure;
+import net.sf.openrocket.gui.scalefigure.ScaleScrollPane;
 import net.sf.openrocket.l10n.Translator;
+import net.sf.openrocket.logging.LogHelper;
+import net.sf.openrocket.optimization.general.OptimizationException;
+import net.sf.openrocket.optimization.general.Point;
 import net.sf.openrocket.optimization.rocketoptimization.OptimizableParameter;
+import net.sf.openrocket.optimization.rocketoptimization.OptimizationGoal;
+import net.sf.openrocket.optimization.rocketoptimization.SimulationDomain;
 import net.sf.openrocket.optimization.rocketoptimization.SimulationModifier;
+import net.sf.openrocket.optimization.rocketoptimization.domains.IdentitySimulationDomain;
+import net.sf.openrocket.optimization.rocketoptimization.domains.StabilityDomain;
+import net.sf.openrocket.optimization.rocketoptimization.goals.MaximizationGoal;
+import net.sf.openrocket.optimization.rocketoptimization.goals.MinimizationGoal;
+import net.sf.openrocket.optimization.rocketoptimization.goals.ValueSeekGoal;
 import net.sf.openrocket.optimization.services.OptimizationServiceHelper;
 import net.sf.openrocket.rocketcomponent.Rocket;
+import net.sf.openrocket.rocketcomponent.RocketComponent;
 import net.sf.openrocket.startup.Application;
+import net.sf.openrocket.unit.CaliberUnit;
+import net.sf.openrocket.unit.Unit;
+import net.sf.openrocket.unit.UnitGroup;
+import net.sf.openrocket.unit.Value;
 import net.sf.openrocket.util.BugException;
+import net.sf.openrocket.util.Chars;
+import net.sf.openrocket.util.FileHelper;
 import net.sf.openrocket.util.GUIUtil;
+import net.sf.openrocket.util.Named;
+import net.sf.openrocket.util.Prefs;
+import net.sf.openrocket.util.TextUtil;
 
+import com.itextpdf.text.Font;
+
+/**
+ * General rocket optimization dialog.
+ * 
+ * @author Sampo Niskanen <sampo.niskanen@iki.fi>
+ */
 public class GeneralOptimizationDialog extends JDialog {
+       private static final LogHelper log = Application.getLogger();
        private static final Translator trans = Application.getTranslator();
        
+       private static final Collator collator = Collator.getInstance();
+       
+
+       private static final String GOAL_MAXIMIZE = trans.get("goal.maximize");
+       private static final String GOAL_MINIMIZE = trans.get("goal.minimize");
+       private static final String GOAL_SEEK = trans.get("goal.seek");
+       
+       private static final String START_TEXT = trans.get("btn.start");
+       private static final String STOP_TEXT = trans.get("btn.stop");
+       
+
+
        private final List<OptimizableParameter> optimizationParameters = new ArrayList<OptimizableParameter>();
        private final Map<Object, List<SimulationModifier>> simulationModifiers =
                        new HashMap<Object, List<SimulationModifier>>();
        
 
        private final OpenRocketDocument baseDocument;
-       private final Rocket rocketCopy;
+       private OpenRocketDocument documentCopy;
+       
+
+       private final JButton addButton;
+       private final JButton removeButton;
+       private final JButton removeAllButton;
+       
+       private final ParameterSelectionTableModel selectedModifierTableModel;
+       private final JTable selectedModifierTable;
+       private final DescriptionArea selectedModifierDescription;
+       private final SimulationModifierTree availableModifierTree;
+       
+       private final JComboBox simulationSelectionCombo;
+       private final JComboBox optimizationParameterCombo;
+       
+       private final JComboBox optimizationGoalCombo;
+       private final JSpinner optimizationGoalSpinner;
+       private final UnitSelector optimizationGoalUnitSelector;
+       private final DoubleModel optimizationSeekValue;
+       
+       private DoubleModel minimumStability;
+       private DoubleModel maximumStability;
+       private final JCheckBox minimumStabilitySelected;
+       private final JSpinner minimumStabilitySpinner;
+       private final UnitSelector minimumStabilityUnitSelector;
+       private final JCheckBox maximumStabilitySelected;
+       private final JSpinner maximumStabilitySpinner;
+       private final UnitSelector maximumStabilityUnitSelector;
        
+       private final JLabel bestValueLabel;
+       private final JLabel stepCountLabel;
+       private final JLabel evaluationCountLabel;
+       private final JLabel stepSizeLabel;
        
+       private final RocketFigure figure;
+       private final JToggleButton startButton;
+       private final JButton plotButton;
+       private final JButton saveButton;
+       
+       private final JButton applyButton;
+       private final JButton resetButton;
+       private final JButton closeButton;
+       
+       private final List<SimulationModifier> selectedModifiers = new ArrayList<SimulationModifier>();
+       
+       /** List of components to disable while optimization is running */
+       private final List<JComponent> disableComponents = new ArrayList<JComponent>();
+       
+       /** Whether optimization is currently running or not */
+       private boolean running = false;
+       /** The optimization worker that is running */
+       private OptimizationWorker worker = null;
+       
+
+       private double bestValue = Double.NaN;
+       private Unit bestValueUnit = Unit.NOUNIT2;
+       private int stepCount = 0;
+       private int evaluationCount = 0;
+       private double stepSize = 0;
+       
+       private final Map<Point, FunctionEvaluationData> evaluationHistory = new LinkedHashMap<Point, FunctionEvaluationData>();
+       private final List<Point> optimizationPath = new LinkedList<Point>();
+       
+
+       private boolean updating = false;
+       
+       
+       /**
+        * Sole constructor.
+        * 
+        * @param document      the document
+        * @param parent        the parent window
+        */
        public GeneralOptimizationDialog(OpenRocketDocument document, Window parent) {
-               super(parent, "Rocket optimization");
+               super(parent, trans.get("title"));
                
                this.baseDocument = document;
-               this.rocketCopy = document.getRocket().copyWithOriginalID();
+               this.documentCopy = document.copy();
                
                loadOptimizationParameters();
                loadSimulationModifiers();
                
-
+               JPanel sub;
+               JLabel label;
+               JScrollPane scroll;
+               String tip;
+               
                JPanel panel = new JPanel(new MigLayout("fill"));
                
 
-               JTree tree = new SimulationModifierTree(rocketCopy, simulationModifiers);
-               JScrollPane scroll = new JScrollPane(tree);
-               panel.add(scroll, "width 300lp, height 300lp");
+               ChangeListener clearHistoryChangeListener = new ChangeListener() {
+                       @Override
+                       public void stateChanged(ChangeEvent e) {
+                               clearHistory();
+                       }
+               };
+               ActionListener clearHistoryActionListener = new ActionListener() {
+                       @Override
+                       public void actionPerformed(ActionEvent e) {
+                               clearHistory();
+                       }
+               };
+               
+
+
+               //// Selected modifiers table
+               
+               selectedModifierTableModel = new ParameterSelectionTableModel();
+               selectedModifierTable = new JTable(selectedModifierTableModel);
+               selectedModifierTable.setDefaultRenderer(Double.class, new DoubleCellRenderer());
+               selectedModifierTable.setRowSelectionAllowed(true);
+               selectedModifierTable.setColumnSelectionAllowed(false);
+               selectedModifierTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
+               
+               // Make sure spinner editor fits into the cell height
+               selectedModifierTable.setRowHeight(new JSpinner().getPreferredSize().height - 4);
+               
+               selectedModifierTable.setDefaultEditor(Double.class, new DoubleCellEditor());
+               selectedModifierTable.setDefaultEditor(Unit.class, new UnitCellEditor() {
+                       @Override
+                       protected UnitGroup getUnitGroup(Unit value, int row, int column) {
+                               return selectedModifiers.get(row).getUnitGroup();
+                       }
+               });
+               
+               disableComponents.add(selectedModifierTable);
+               
+               selectedModifierTable.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
+                       @Override
+                       public void valueChanged(ListSelectionEvent e) {
+                               updateComponents();
+                       }
+               });
+               
+               // Set column widths
+               TableColumnModel columnModel = selectedModifierTable.getColumnModel();
+               columnModel.getColumn(0).setPreferredWidth(150);
+               columnModel.getColumn(1).setPreferredWidth(40);
+               columnModel.getColumn(2).setPreferredWidth(40);
+               columnModel.getColumn(3).setPreferredWidth(40);
+               
+               scroll = new JScrollPane(selectedModifierTable);
+               
+               label = new StyledLabel(trans.get("lbl.paramsToOptimize"), Style.BOLD);
+               disableComponents.add(label);
+               panel.add(label, "split 3, flowy");
+               panel.add(scroll, "wmin 300lp, height 200lp, grow");
+               selectedModifierDescription = new DescriptionArea(2, -3);
+               disableComponents.add(selectedModifierDescription);
+               panel.add(selectedModifierDescription, "growx");
+               
+
+
+               //// Add/remove buttons
+               sub = new JPanel(new MigLayout("fill"));
+               
+               addButton = new JButton(Chars.LEFT_ARROW + " " + trans.get("btn.add") + "   ");
+               addButton.setToolTipText(trans.get("btn.add.ttip"));
+               addButton.addActionListener(new ActionListener() {
+                       @Override
+                       public void actionPerformed(ActionEvent e) {
+                               SimulationModifier mod = getSelectedAvailableModifier();
+                               if (mod != null) {
+                                       addModifier(mod);
+                                       clearHistory();
+                               } else {
+                                       log.error("Attempting to add simulation modifier when none is selected");
+                               }
+                       }
+               });
+               disableComponents.add(addButton);
+               sub.add(addButton, "wrap para, sg button");
+               
+               removeButton = new JButton("   " + trans.get("btn.remove") + " " + Chars.RIGHT_ARROW);
+               removeButton.setToolTipText(trans.get("btn.remove.ttip"));
+               removeButton.addActionListener(new ActionListener() {
+                       @Override
+                       public void actionPerformed(ActionEvent e) {
+                               SimulationModifier mod = getSelectedModifier();
+                               if (mod == null) {
+                                       log.error("Attempting to remove simulation modifier when none is selected");
+                                       return;
+                               }
+                               removeModifier(mod);
+                               clearHistory();
+                       }
+               });
+               disableComponents.add(removeButton);
+               sub.add(removeButton, "wrap para*2, sg button");
+               
+               removeAllButton = new JButton(trans.get("btn.removeAll"));
+               removeAllButton.setToolTipText(trans.get("btn.removeAll.ttip"));
+               removeAllButton.addActionListener(new ActionListener() {
+                       @Override
+                       public void actionPerformed(ActionEvent e) {
+                               log.user("Removing all selected modifiers");
+                               selectedModifiers.clear();
+                               selectedModifierTableModel.fireTableDataChanged();
+                               availableModifierTree.repaint();
+                               clearHistory();
+                       }
+               });
+               disableComponents.add(removeAllButton);
+               sub.add(removeAllButton, "wrap para, sg button");
+               
+               panel.add(sub);
+               
+
+
+               //// Available modifier tree
+               availableModifierTree = new SimulationModifierTree(documentCopy.getRocket(), simulationModifiers, selectedModifiers);
+               availableModifierTree.getSelectionModel().addTreeSelectionListener(new TreeSelectionListener() {
+                       @Override
+                       public void valueChanged(TreeSelectionEvent e) {
+                               updateComponents();
+                       }
+               });
+               
+               // Handle double-click
+               availableModifierTree.addMouseListener(new MouseAdapter() {
+                       @Override
+                       public void mousePressed(MouseEvent e) {
+                               if (e.getClickCount() == 2) {
+                                       SimulationModifier mod = getSelectedAvailableModifier();
+                                       if (mod != null) {
+                                               addModifier(mod);
+                                               clearHistory();
+                                       } else {
+                                               log.user("Double-clicked non-available option");
+                                       }
+                               }
+                       }
+               });
+               
+               disableComponents.add(availableModifierTree);
+               scroll = new JScrollPane(availableModifierTree);
+               label = new StyledLabel(trans.get("lbl.availableParams"), Style.BOLD);
+               disableComponents.add(label);
+               panel.add(label, "split 2, flowy");
+               panel.add(scroll, "width 300lp, height 200lp, grow, wrap para*2");
+               
+
+
+
+               ////  Optimization options sub-panel
+               
+               sub = new JPanel(new MigLayout("fill"));
+               TitledBorder border = BorderFactory.createTitledBorder(trans.get("lbl.optimizationOpts"));
+               GUIUtil.changeFontStyle(border, Font.BOLD);
+               sub.setBorder(border);
+               disableComponents.add(sub);
+               
+
+               //// Simulation to optimize
+               
+               label = new JLabel(trans.get("lbl.optimizeSim"));
+               tip = trans.get("lbl.optimizeSim.ttip");
+               label.setToolTipText(tip);
+               disableComponents.add(label);
+               sub.add(label, "");
+               
+               simulationSelectionCombo = new JComboBox();
+               simulationSelectionCombo.setToolTipText(tip);
+               populateSimulations();
+               simulationSelectionCombo.addActionListener(clearHistoryActionListener);
+               disableComponents.add(simulationSelectionCombo);
+               sub.add(simulationSelectionCombo, "growx, wrap unrel");
+               
+
+
+               //// Value to optimize
+               label = new JLabel(trans.get("lbl.optimizeValue"));
+               tip = trans.get("lbl.optimizeValue.ttip");
+               label.setToolTipText(tip);
+               disableComponents.add(label);
+               sub.add(label, "");
+               
+               optimizationParameterCombo = new JComboBox();
+               optimizationParameterCombo.setToolTipText(tip);
+               populateParameters();
+               optimizationParameterCombo.addActionListener(clearHistoryActionListener);
+               disableComponents.add(optimizationParameterCombo);
+               sub.add(optimizationParameterCombo, "growx, wrap unrel");
+               
+
+
+               //// Optimization goal
+               label = new JLabel(trans.get("lbl.optimizeGoal"));
+               tip = trans.get("lbl.optimizeGoal");
+               label.setToolTipText(tip);
+               disableComponents.add(label);
+               sub.add(label, "");
+               
+               optimizationGoalCombo = new JComboBox(new String[] { GOAL_MAXIMIZE, GOAL_MINIMIZE, GOAL_SEEK });
+               optimizationGoalCombo.setToolTipText(tip);
+               optimizationGoalCombo.setEditable(false);
+               optimizationGoalCombo.addActionListener(clearHistoryActionListener);
+               disableComponents.add(optimizationGoalCombo);
+               sub.add(optimizationGoalCombo, "growx");
+               
+
+               //// Optimization custom value
+               optimizationSeekValue = new DoubleModel(0, UnitGroup.UNITS_NONE);
+               optimizationSeekValue.addChangeListener(clearHistoryChangeListener);
+               
+               optimizationGoalSpinner = new JSpinner(optimizationSeekValue.getSpinnerModel());
+               tip = trans.get("lbl.optimizeGoalValue.ttip");
+               optimizationGoalSpinner.setToolTipText(tip);
+               optimizationGoalSpinner.setEditor(new SpinnerEditor(optimizationGoalSpinner));
+               disableComponents.add(optimizationGoalSpinner);
+               sub.add(optimizationGoalSpinner, "width 30lp");
+               
+               optimizationGoalUnitSelector = new UnitSelector(optimizationSeekValue);
+               optimizationGoalUnitSelector.setToolTipText(tip);
+               disableComponents.add(optimizationGoalUnitSelector);
+               sub.add(optimizationGoalUnitSelector, "width 20lp, wrap unrel");
+               
+
+               panel.add(sub, "grow");
+               
+
+
+               ////  Required stability sub-panel
+               
+               sub = new JPanel(new MigLayout("fill"));
+               border = BorderFactory.createTitledBorder(trans.get("lbl.requireStability"));
+               GUIUtil.changeFontStyle(border, Font.BOLD);
+               sub.setBorder(border);
+               disableComponents.add(sub);
+               
+
+
+               double ref = CaliberUnit.calculateCaliber(baseDocument.getRocket());
+               minimumStability = new DoubleModel(ref, UnitGroup.stabilityUnits(ref));
+               maximumStability = new DoubleModel(5 * ref, UnitGroup.stabilityUnits(ref));
+               minimumStability.addChangeListener(clearHistoryChangeListener);
+               maximumStability.addChangeListener(clearHistoryChangeListener);
+               
+
+               //// Minimum stability
+               tip = trans.get("lbl.requireMinStability.ttip");
+               minimumStabilitySelected = new JCheckBox(trans.get("lbl.requireMinStability"));
+               minimumStabilitySelected.setSelected(true);
+               minimumStabilitySelected.setToolTipText(tip);
+               minimumStabilitySelected.addActionListener(new ActionListener() {
+                       @Override
+                       public void actionPerformed(ActionEvent e) {
+                               updateComponents();
+                       }
+               });
+               disableComponents.add(minimumStabilitySelected);
+               sub.add(minimumStabilitySelected);
+               
+               minimumStabilitySpinner = new JSpinner(minimumStability.getSpinnerModel());
+               minimumStabilitySpinner.setToolTipText(tip);
+               minimumStabilitySpinner.setEditor(new SpinnerEditor(minimumStabilitySpinner));
+               disableComponents.add(minimumStabilitySpinner);
+               sub.add(minimumStabilitySpinner, "growx");
+               
+               minimumStabilityUnitSelector = new UnitSelector(minimumStability);
+               minimumStabilityUnitSelector.setToolTipText(tip);
+               disableComponents.add(minimumStabilityUnitSelector);
+               sub.add(minimumStabilityUnitSelector, "growx, wrap unrel");
+               
+
+               //// Maximum stability
+               tip = trans.get("lbl.requireMaxStability.ttip");
+               maximumStabilitySelected = new JCheckBox(trans.get("lbl.requireMaxStability"));
+               maximumStabilitySelected.setToolTipText(tip);
+               maximumStabilitySelected.addActionListener(new ActionListener() {
+                       @Override
+                       public void actionPerformed(ActionEvent e) {
+                               updateComponents();
+                       }
+               });
+               disableComponents.add(maximumStabilitySelected);
+               sub.add(maximumStabilitySelected);
+               
+               maximumStabilitySpinner = new JSpinner(maximumStability.getSpinnerModel());
+               maximumStabilitySpinner.setToolTipText(tip);
+               maximumStabilitySpinner.setEditor(new SpinnerEditor(maximumStabilitySpinner));
+               disableComponents.add(maximumStabilitySpinner);
+               sub.add(maximumStabilitySpinner, "growx");
+               
+               maximumStabilityUnitSelector = new UnitSelector(maximumStability);
+               maximumStabilityUnitSelector.setToolTipText(tip);
+               disableComponents.add(maximumStabilityUnitSelector);
+               sub.add(maximumStabilityUnitSelector, "growx, wrap para");
+               
+
+
+               //              DescriptionArea desc = new DescriptionArea("Stability requirements are verified during each time step of the simulation.",
+               //                              2, -2, false);
+               //              desc.setViewportBorder(null);
+               //              disableComponents.add(desc);
+               //              sub.add(desc, "span, growx");
+               
+
+               panel.add(sub, "span 2, grow, wrap para*2");
+               
+
+
+
+               ////  Rocket figure
+               figure = new RocketFigure(getSelectedSimulation().getConfiguration());
+               figure.setBorderPixels(1, 1);
+               ScaleScrollPane figureScrollPane = new ScaleScrollPane(figure);
+               figureScrollPane.setFitting(true);
+               panel.add(figureScrollPane, "span, split, height 200lp, grow");
+               
+
+               sub = new JPanel(new MigLayout("fill"));
+               
+
+               label = new JLabel(trans.get("status.bestValue"));
+               tip = trans.get("status.bestValue.ttip");
+               label.setToolTipText(tip);
+               sub.add(label, "gapright unrel");
+               
+               bestValueLabel = new JLabel();
+               bestValueLabel.setToolTipText(tip);
+               sub.add(bestValueLabel, "wmin 60lp, wrap rel");
+               
+
+               label = new JLabel(trans.get("status.stepCount"));
+               tip = trans.get("status.stepCount.ttip");
+               label.setToolTipText(tip);
+               sub.add(label, "gapright unrel");
+               
+               stepCountLabel = new JLabel();
+               stepCountLabel.setToolTipText(tip);
+               sub.add(stepCountLabel, "wrap rel");
+               
+
+               label = new JLabel(trans.get("status.evalCount"));
+               tip = trans.get("status.evalCount");
+               label.setToolTipText(tip);
+               sub.add(label, "gapright unrel");
+               
+               evaluationCountLabel = new JLabel();
+               evaluationCountLabel.setToolTipText(tip);
+               sub.add(evaluationCountLabel, "wrap rel");
+               
+
+               label = new JLabel(trans.get("status.stepSize"));
+               tip = trans.get("status.stepSize.ttip");
+               label.setToolTipText(tip);
+               sub.add(label, "gapright unrel");
+               
+               stepSizeLabel = new JLabel();
+               stepSizeLabel.setToolTipText(tip);
+               sub.add(stepSizeLabel, "wrap para");
+               
+
+               //// Start/Stop button
+               
+               startButton = new JToggleButton(START_TEXT);
+               startButton.addActionListener(new ActionListener() {
+                       @Override
+                       public void actionPerformed(ActionEvent e) {
+                               if (updating) {
+                                       log.debug("Updating, ignoring event");
+                                       return;
+                               }
+                               if (running) {
+                                       log.user("Stopping optimization");
+                                       stopOptimization();
+                               } else {
+                                       log.user("Starting optimization");
+                                       startOptimization();
+                               }
+                       }
+               });
+               sub.add(startButton, "span, growx, wrap para*2");
+               
+
+               plotButton = new JButton(trans.get("btn.plotPath"));
+               plotButton.setToolTipText(trans.get("btn.plotPath.ttip"));
+               plotButton.addActionListener(new ActionListener() {
+                       @Override
+                       public void actionPerformed(ActionEvent e) {
+                               log.user("Plotting optimization path, dimensionality=" + selectedModifiers.size());
+                               OptimizationPlotDialog dialog = new OptimizationPlotDialog(optimizationPath, evaluationHistory,
+                                               selectedModifiers, getSelectedParameter(),
+                                               UnitGroup.stabilityUnits(getSelectedSimulation().getRocket()),
+                                               GeneralOptimizationDialog.this);
+                               dialog.setVisible(true);
+                       }
+               });
+               disableComponents.add(plotButton);
+               sub.add(plotButton, "span, growx, wrap");
+               
+
+               saveButton = new JButton(trans.get("btn.save"));
+               saveButton.setToolTipText(trans.get("btn.save.ttip"));
+               saveButton.addActionListener(new ActionListener() {
+                       @Override
+                       public void actionPerformed(ActionEvent e) {
+                               log.user("User selected save path");
+                               savePath();
+                       }
+               });
+               disableComponents.add(saveButton);
+               sub.add(saveButton, "span, growx");
+               
+
+
+               panel.add(sub, "wrap para*2");
+               
+
+
+
+               ////  Bottom buttons
+               
+               applyButton = new JButton(trans.get("btn.apply"));
+               applyButton.setToolTipText(trans.get("btn.apply.ttip"));
+               applyButton.addActionListener(new ActionListener() {
+                       @Override
+                       public void actionPerformed(ActionEvent e) {
+                               log.user("Applying optimization changes");
+                               applyDesign();
+                       }
+               });
+               disableComponents.add(applyButton);
+               panel.add(applyButton, "span, split, gapright para, right");
+               
+               resetButton = new JButton(trans.get("btn.reset"));
+               resetButton.setToolTipText(trans.get("btn.reset.ttip"));
+               resetButton.addActionListener(new ActionListener() {
+                       @Override
+                       public void actionPerformed(ActionEvent e) {
+                               log.user("Resetting optimization design");
+                               resetDesign();
+                       }
+               });
+               disableComponents.add(resetButton);
+               panel.add(resetButton, "gapright para, right");
+               
+               closeButton = new JButton(trans.get("btn.close"));
+               closeButton.setToolTipText(trans.get("btn.close.ttip"));
+               closeButton.addActionListener(new ActionListener() {
+                       @Override
+                       public void actionPerformed(ActionEvent e) {
+                               log.user("Closing optimization dialog");
+                               stopOptimization();
+                               GeneralOptimizationDialog.this.dispose();
+                       }
+               });
+               panel.add(closeButton, "right");
                
 
                this.add(panel);
+               clearHistory();
+               updateComponents();
                GUIUtil.setDisposableDialogOptions(this, null);
        }
        
        
+       private void startOptimization() {
+               if (running) {
+                       log.info("Optimization already running");
+                       return;
+               }
+               
+
+               if (selectedModifiers.isEmpty()) {
+                       JOptionPane.showMessageDialog(this, trans.get("error.selectParams.text"),
+                                       trans.get("error.selectParams.title"), JOptionPane.ERROR_MESSAGE);
+                       updating = true;
+                       startButton.setSelected(false);
+                       startButton.setText(START_TEXT);
+                       updating = false;
+                       return;
+               }
+               
+
+               running = true;
+               
+               // Update the button status
+               updating = true;
+               startButton.setSelected(true);
+               startButton.setText(STOP_TEXT);
+               updating = false;
+               
+
+               // Create a copy of the simulation (we're going to modify the original in the current thread)
+               Simulation simulation = getSelectedSimulation();
+               Rocket rocketCopy = simulation.getRocket().copyWithOriginalID();
+               simulation = simulation.duplicateSimulation(rocketCopy);
+               
+               OptimizableParameter parameter = getSelectedParameter();
+               
+               OptimizationGoal goal;
+               String value = (String) optimizationGoalCombo.getSelectedItem();
+               if (GOAL_MAXIMIZE.equals(value)) {
+                       goal = new MaximizationGoal();
+               } else if (GOAL_MINIMIZE.equals(value)) {
+                       goal = new MinimizationGoal();
+               } else if (GOAL_SEEK.equals(value)) {
+                       goal = new ValueSeekGoal(optimizationSeekValue.getValue());
+               } else {
+                       throw new BugException("optimizationGoalCombo had invalid value: " + value);
+               }
+               
+               SimulationDomain domain;
+               if (minimumStabilitySelected.isSelected() || maximumStabilitySelected.isSelected()) {
+                       double min, max;
+                       boolean minAbsolute, maxAbsolute;
+                       
+                       /*
+                        * Make minAbsolute/maxAbsolute consistent with each other to produce reasonable
+                        * result in plot tool tips.  Yes, this is a bit ugly.
+                        */
+
+                       // Min stability
+                       Unit unit = minimumStability.getCurrentUnit();
+                       if (unit instanceof CaliberUnit) {
+                               min = unit.toUnit(minimumStability.getValue());
+                               minAbsolute = false;
+                       } else {
+                               min = minimumStability.getValue();
+                               minAbsolute = true;
+                       }
+                       
+                       // Max stability
+                       unit = maximumStability.getCurrentUnit();
+                       if (unit instanceof CaliberUnit) {
+                               max = unit.toUnit(maximumStability.getValue());
+                               maxAbsolute = false;
+                       } else {
+                               max = maximumStability.getValue();
+                               maxAbsolute = true;
+                       }
+                       
+
+                       if (!minimumStabilitySelected.isSelected()) {
+                               min = Double.NaN;
+                               minAbsolute = maxAbsolute;
+                       }
+                       if (!maximumStabilitySelected.isSelected()) {
+                               max = Double.NaN;
+                               maxAbsolute = minAbsolute;
+                       }
+                       
+                       domain = new StabilityDomain(min, minAbsolute, max, maxAbsolute);
+               } else {
+                       domain = new IdentitySimulationDomain();
+               }
+               
+               SimulationModifier[] modifiers = selectedModifiers.toArray(new SimulationModifier[0]);
+               
+               // Create and start the background worker
+               worker = new OptimizationWorker(simulation, parameter, goal, domain, modifiers) {
+                       @Override
+                       protected void done(OptimizationException exception) {
+                               log.info("Optimization finished, exception=" + exception, exception);
+                               
+                               if (exception != null) {
+                                       JOptionPane.showMessageDialog(GeneralOptimizationDialog.this,
+                                                       new Object[] {
+                                                                       trans.get("error.optimizationFailure.text"),
+                                                                       exception.getLocalizedMessage()
+                                       }, trans.get("error.optimizationFailure.title"), JOptionPane.ERROR_MESSAGE);
+                               }
+                               
+                               worker = null;
+                               stopOptimization();
+                       }
+                       
+                       @Override
+                       protected void functionEvaluated(List<FunctionEvaluationData> data) {
+                               for (FunctionEvaluationData d : data) {
+                                       evaluationHistory.put(d.getPoint(), d);
+                                       evaluationCount++;
+                               }
+                               updateCounters();
+                       }
+                       
+                       @Override
+                       protected void optimizationStepTaken(List<OptimizationStepData> data) {
+                               
+                               // Add starting point to the path
+                               if (optimizationPath.isEmpty()) {
+                                       optimizationPath.add(data.get(0).getOldPoint());
+                               }
+                               
+                               // Add other points to the path
+                               for (OptimizationStepData d : data) {
+                                       optimizationPath.add(d.getNewPoint());
+                               }
+                               
+                               // Get function value from the latest point
+                               OptimizationStepData latest = data.get(data.size() - 1);
+                               Point newPoint = latest.getNewPoint();
+                               
+                               FunctionEvaluationData pointValue = evaluationHistory.get(newPoint);
+                               if (pointValue != null) {
+                                       bestValue = pointValue.getParameterValue().getValue();
+                               } else {
+                                       log.error("History does not contain point " + newPoint);
+                                       bestValue = Double.NaN;
+                               }
+                               
+                               // Update the simulation
+                               Simulation sim = getSelectedSimulation();
+                               for (int i = 0; i < newPoint.dim(); i++) {
+                                       try {
+                                               selectedModifiers.get(i).modify(sim, newPoint.get(i));
+                                       } catch (OptimizationException e) {
+                                               throw new BugException("Simulation modifier failed to modify the base simulation " +
+                                                               "modifier=" + selectedModifiers.get(i), e);
+                                       }
+                               }
+                               figure.updateFigure();
+                               
+                               // Update other counter data
+                               stepCount += data.size();
+                               stepSize = latest.getStepSize();
+                               updateCounters();
+                       }
+               };
+               worker.start();
+               
+
+               clearHistory();
+               
+               updateComponents();
+       }
+       
+       private void stopOptimization() {
+               if (!running) {
+                       log.info("Optimization not running");
+                       return;
+               }
+               
+               if (worker != null && worker.isAlive()) {
+                       log.info("Worker still running, interrupting it and setting to null");
+                       worker.interrupt();
+                       worker = null;
+                       return;
+               }
+               
+               running = false;
+               
+               // Update the button status
+               updating = true;
+               startButton.setSelected(false);
+               startButton.setText(START_TEXT);
+               updating = false;
+               
+
+               updateComponents();
+       }
+       
+       
+
+
+       /**
+        * Reset the current optimization history and values.  This does not reset the design.
+        */
+       private void clearHistory() {
+               evaluationHistory.clear();
+               optimizationPath.clear();
+               bestValue = Double.NaN;
+               bestValueUnit = getSelectedParameter().getUnitGroup().getDefaultUnit();
+               stepCount = 0;
+               evaluationCount = 0;
+               stepSize = 0.5;
+               updateCounters();
+               updateComponents();
+       }
+       
+       
+       private void applyDesign() {
+               // TODO: MEDIUM: Apply also potential changes to simulations
+               Rocket src = getSelectedSimulation().getRocket().copyWithOriginalID();
+               Rocket dest = baseDocument.getRocket();
+               try {
+                       baseDocument.startUndo(trans.get("undoText"));
+                       dest.freeze();
+                       
+                       // Remove all children
+                       while (dest.getChildCount() > 0) {
+                               dest.removeChild(0);
+                       }
+                       
+                       // Move all children to the destination rocket
+                       while (src.getChildCount() > 0) {
+                               RocketComponent c = src.getChild(0);
+                               src.removeChild(0);
+                               dest.addChild(c);
+                       }
+                       
+               } finally {
+                       dest.thaw();
+                       baseDocument.stopUndo();
+               }
+       }
+       
+       
+       private void resetDesign() {
+               clearHistory();
+               
+               documentCopy = baseDocument.copy();
+               
+               loadOptimizationParameters();
+               loadSimulationModifiers();
+               
+               // Replace selected modifiers with corresponding new modifiers
+               List<SimulationModifier> newSelected = new ArrayList<SimulationModifier>();
+               for (SimulationModifier original : selectedModifiers) {
+                       List<SimulationModifier> newModifiers = simulationModifiers.get(original.getRelatedObject());
+                       if (newModifiers != null) {
+                               int index = newModifiers.indexOf(original);
+                               if (index >= 0) {
+                                       newSelected.add(newModifiers.get(index));
+                               }
+                       }
+               }
+               selectedModifiers.clear();
+               selectedModifiers.addAll(newSelected);
+               selectedModifierTableModel.fireTableDataChanged();
+               
+               // Update the available modifier tree
+               availableModifierTree.populateTree(documentCopy.getRocket(), simulationModifiers);
+               availableModifierTree.expandComponents();
+               
+
+               // Update selectable simulations
+               populateSimulations();
+               
+               // Update selectable parameters
+               populateParameters();
+               
+       }
+       
+       
+       private void populateSimulations() {
+               String current = null;
+               Object selection = simulationSelectionCombo.getSelectedItem();
+               if (selection != null) {
+                       current = selection.toString();
+               }
+               
+
+               List<Named<Simulation>> simulations = new ArrayList<Named<Simulation>>();
+               Rocket rocket = documentCopy.getRocket();
+               
+               for (Simulation s : documentCopy.getSimulations()) {
+                       String id = s.getConfiguration().getMotorConfigurationID();
+                       String name = createSimulationName(s.getName(), rocket.getMotorConfigurationNameOrDescription(id));
+                       simulations.add(new Named<Simulation>(s, name));
+               }
+               
+               for (String id : rocket.getMotorConfigurationIDs()) {
+                       if (id == null) {
+                               continue;
+                       }
+                       Simulation sim = new Simulation(rocket);
+                       sim.getConfiguration().setMotorConfigurationID(id);
+                       String name = createSimulationName(trans.get("basicSimulationName"), rocket.getMotorConfigurationNameOrDescription(id));
+                       simulations.add(new Named<Simulation>(sim, name));
+               }
+               
+
+               Simulation sim = new Simulation(rocket);
+               sim.getConfiguration().setMotorConfigurationID(null);
+               String name = createSimulationName(trans.get("noSimulationName"), rocket.getMotorConfigurationNameOrDescription(null));
+               simulations.add(new Named<Simulation>(sim, name));
+               
+
+               simulationSelectionCombo.setModel(new DefaultComboBoxModel(simulations.toArray()));
+               
+               if (current != null) {
+                       for (int i = 0; i < simulations.size(); i++) {
+                               if (simulations.get(i).toString().equals(current)) {
+                                       simulationSelectionCombo.setSelectedIndex(i);
+                                       break;
+                               }
+                       }
+               }
+       }
+       
+       
+       private void populateParameters() {
+               String current = null;
+               Object selection = optimizationParameterCombo.getSelectedItem();
+               if (selection != null) {
+                       current = selection.toString();
+               } else {
+                       // Default to apogee altitude event if it is not the first one in the list
+                       current = trans.get("MaximumAltitudeParameter.name");
+               }
+               
+               List<Named<OptimizableParameter>> parameters = new ArrayList<Named<OptimizableParameter>>();
+               for (OptimizableParameter p : optimizationParameters) {
+                       parameters.add(new Named<OptimizableParameter>(p, p.getName()));
+               }
+               
+               optimizationParameterCombo.setModel(new DefaultComboBoxModel(parameters.toArray()));
+               
+               for (int i = 0; i < parameters.size(); i++) {
+                       if (parameters.get(i).toString().equals(current)) {
+                               optimizationParameterCombo.setSelectedIndex(i);
+                               break;
+                       }
+               }
+       }
+       
+       private void updateCounters() {
+               bestValueLabel.setText(bestValueUnit.toStringUnit(bestValue));
+               stepCountLabel.setText("" + stepCount);
+               evaluationCountLabel.setText("" + evaluationCount);
+               stepSizeLabel.setText(UnitGroup.UNITS_RELATIVE.toStringUnit(stepSize));
+       }
+       
+       
        private void loadOptimizationParameters() {
-               optimizationParameters.addAll(OptimizationServiceHelper.getOptimizableParameters(baseDocument));
+               optimizationParameters.clear();
+               optimizationParameters.addAll(OptimizationServiceHelper.getOptimizableParameters(documentCopy));
                
                if (optimizationParameters.isEmpty()) {
                        throw new BugException("No rocket optimization parameters found, distribution built wrong.");
@@ -76,8 +1057,9 @@ public class GeneralOptimizationDialog extends JDialog {
        
        
        private void loadSimulationModifiers() {
+               simulationModifiers.clear();
                
-               for (SimulationModifier m : OptimizationServiceHelper.getSimulationModifiers(baseDocument)) {
+               for (SimulationModifier m : OptimizationServiceHelper.getSimulationModifiers(documentCopy)) {
                        Object key = m.getRelatedObject();
                        List<SimulationModifier> list = simulationModifiers.get(key);
                        if (list == null) {
@@ -99,6 +1081,464 @@ public class GeneralOptimizationDialog extends JDialog {
                
        }
        
+       
+
+       private void addModifier(SimulationModifier mod) {
+               if (!selectedModifiers.contains(mod)) {
+                       log.user(1, "Adding simulation modifier " + mod);
+                       selectedModifiers.add(mod);
+                       Collections.sort(selectedModifiers, new SimulationModifierComparator());
+                       selectedModifierTableModel.fireTableDataChanged();
+                       availableModifierTree.repaint();
+               } else {
+                       log.user(1, "Attempting to add an already existing simulation modifier " + mod);
+               }
+       }
+       
+       
+       private void removeModifier(SimulationModifier mod) {
+               log.user(1, "Removing simulation modifier " + mod);
+               selectedModifiers.remove(mod);
+               selectedModifierTableModel.fireTableDataChanged();
+               availableModifierTree.repaint();
+       }
+       
+       
+
+       /**
+        * Update the enabled status of all components in the dialog.
+        */
+       private void updateComponents() {
+               
+               if (updating) {
+                       return;
+               }
+               
+               updating = true;
+               
+
+               // First enable all components if optimization not running
+               if (!running) {
+                       for (JComponent c : disableComponents) {
+                               c.setEnabled(true);
+                       }
+               }
+               
+
+               // "Add" button
+               SimulationModifier mod = getSelectedAvailableModifier();
+               if (mod != null && !selectedModifiers.contains(mod)) {
+                       addButton.setEnabled(true);
+               } else {
+                       addButton.setEnabled(false);
+               }
+               
+               // "Remove" button
+               removeButton.setEnabled(selectedModifierTable.getSelectedRow() >= 0);
+               
+               // "Remove all" button
+               removeAllButton.setEnabled(!selectedModifiers.isEmpty());
+               
+
+               // Optimization goal
+               String selected = (String) optimizationGoalCombo.getSelectedItem();
+               if (GOAL_SEEK.equals(selected)) {
+                       optimizationGoalSpinner.setVisible(true);
+                       optimizationGoalUnitSelector.setVisible(true);
+               } else {
+                       optimizationGoalSpinner.setVisible(false);
+                       optimizationGoalUnitSelector.setVisible(false);
+               }
+               
+
+               // Minimum/maximum stability options
+               minimumStabilitySpinner.setEnabled(minimumStabilitySelected.isSelected());
+               minimumStabilityUnitSelector.setEnabled(minimumStabilitySelected.isSelected());
+               maximumStabilitySpinner.setEnabled(maximumStabilitySelected.isSelected());
+               maximumStabilityUnitSelector.setEnabled(maximumStabilitySelected.isSelected());
+               
+
+               // Plot button (enabled if path exists and dimensionality is 1 or 2)
+               plotButton.setEnabled(!optimizationPath.isEmpty() && (selectedModifiers.size() == 1 || selectedModifiers.size() == 2));
+               
+               // Save button (enabled if path exists)
+               saveButton.setEnabled(!optimizationPath.isEmpty());
+               
+
+               // Last disable all components if optimization is running
+               if (running) {
+                       for (JComponent c : disableComponents) {
+                               c.setEnabled(false);
+                       }
+               }
+               
+
+               // Update description text
+               mod = getSelectedModifier();
+               if (mod != null) {
+                       selectedModifierDescription.setText(mod.getDescription());
+               } else {
+                       selectedModifierDescription.setText("");
+               }
+               
+
+               // Update the figure
+               figure.setConfiguration(getSelectedSimulation().getConfiguration());
+               
+               updating = false;
+       }
+       
+       
+       private void savePath() {
+               
+               if (evaluationHistory.isEmpty()) {
+                       throw new BugException("evaluation history is empty");
+               }
+               
+               CsvOptionPanel csvOptions = new CsvOptionPanel(GeneralOptimizationDialog.class,
+                               trans.get("export.header"), trans.get("export.header.ttip"));
+               
+
+               JFileChooser chooser = new JFileChooser();
+               chooser.setFileFilter(FileHelper.CSV_FILE_FILTER);
+               chooser.setCurrentDirectory(Prefs.getDefaultDirectory());
+               chooser.setAccessory(csvOptions);
+               
+               if (chooser.showSaveDialog(this) != JFileChooser.APPROVE_OPTION)
+                       return;
+               
+               File file = chooser.getSelectedFile();
+               if (file == null)
+                       return;
+               
+               file = FileHelper.ensureExtension(file, "csv");
+               if (!FileHelper.confirmWrite(file, this)) {
+                       return;
+               }
+               
+               String fieldSeparator = csvOptions.getFieldSeparator();
+               String commentCharacter = csvOptions.getCommentCharacter();
+               boolean includeHeader = csvOptions.getSelectionOption(0);
+               csvOptions.storePreferences();
+               
+               log.info("Saving optimization path to " + file + ", fieldSeparator=" + fieldSeparator +
+                               ", commentCharacter=" + commentCharacter + ", includeHeader=" + includeHeader);
+               
+               try {
+                       Writer writer = new BufferedWriter(new FileWriter(file));
+                       
+                       // Write header
+                       if (includeHeader) {
+                               FunctionEvaluationData data = evaluationHistory.values().iterator().next();
+                               
+                               writer.write(commentCharacter);
+                               for (SimulationModifier mod : selectedModifiers) {
+                                       writer.write(mod.getRelatedObject().toString() + ": " + mod.getName() + " / " +
+                                                       mod.getUnitGroup().getDefaultUnit().getUnit());
+                                       writer.write(fieldSeparator);
+                               }
+                               if (minimumStabilitySelected.isSelected() || maximumStabilitySelected.isSelected()) {
+                                       writer.write(trans.get("export.stability") + " / " + data.getDomainReference().getUnit().getUnit());
+                                       writer.write(fieldSeparator);
+                               }
+                               writer.write(getSelectedParameter().getName() + " / " +
+                                               getSelectedParameter().getUnitGroup().getDefaultUnit().getUnit());
+                               
+                               writer.write("\n");
+                       }
+                       
+
+                       for (FunctionEvaluationData data : evaluationHistory.values()) {
+                               Value[] state = data.getState();
+                               
+                               for (int i = 0; i < state.length; i++) {
+                                       writer.write(TextUtil.doubleToString(state[i].getUnitValue()));
+                                       writer.write(fieldSeparator);
+                               }
+                               
+                               if (minimumStabilitySelected.isSelected() || maximumStabilitySelected.isSelected()) {
+                                       writer.write(TextUtil.doubleToString(data.getDomainReference().getUnitValue()));
+                                       writer.write(fieldSeparator);
+                               }
+                               
+                               if (data.getParameterValue() != null) {
+                                       writer.write(TextUtil.doubleToString(data.getParameterValue().getUnitValue()));
+                               } else {
+                                       writer.write("N/A");
+                               }
+                               writer.write("\n");
+                       }
+                       
+                       writer.close();
+                       log.info("File successfully saved");
+                       
+               } catch (IOException e) {
+                       FileHelper.errorWriting(e, this);
+               }
+               
+       }
+       
+       /**
+        * Return the currently selected available simulation modifier from the modifier tree,
+        * or <code>null</code> if none selected.
+        */
+       private SimulationModifier getSelectedAvailableModifier() {
+               TreePath treepath = availableModifierTree.getSelectionPath();
+               if (treepath != null) {
+                       Object o = ((DefaultMutableTreeNode) treepath.getLastPathComponent()).getUserObject();
+                       if (o instanceof SimulationModifier) {
+                               return (SimulationModifier) o;
+                       }
+               }
+               return null;
+       }
+       
+       /**
+        * Return the currently selected simulation.
+        * @return      the selected simulation.
+        */
+       @SuppressWarnings("unchecked")
+       private Simulation getSelectedSimulation() {
+               return ((Named<Simulation>) simulationSelectionCombo.getSelectedItem()).get();
+       }
+       
+       
+       /**
+        * Return the currently selected simulation modifier from the table,
+        * or <code>null</code> if none selected.
+        * @return      the selected modifier or <code>null</code>.
+        */
+       private SimulationModifier getSelectedModifier() {
+               int row = selectedModifierTable.getSelectedRow();
+               if (row < 0) {
+                       return null;
+               }
+               row = selectedModifierTable.convertRowIndexToModel(row);
+               return selectedModifiers.get(row);
+       }
+       
+       
+       /**
+        * Return the currently selected optimization parameter.
+        * @return      the selected optimization parameter.
+        */
+       @SuppressWarnings("unchecked")
+       private OptimizableParameter getSelectedParameter() {
+               return ((Named<OptimizableParameter>) optimizationParameterCombo.getSelectedItem()).get();
+       }
+       
+       
+       private Unit getModifierUnit(int index) {
+               return selectedModifiers.get(index).getUnitGroup().getDefaultUnit();
+       }
+       
+       private String createSimulationName(String simulationName, String motorConfiguration) {
+               String name;
+               boolean hasParenthesis = motorConfiguration.matches("^[\\[\\(].*[\\]\\)]$");
+               name = simulationName + " ";
+               if (!hasParenthesis) {
+                       name += "(";
+               }
+               name += motorConfiguration;
+               if (!hasParenthesis) {
+                       name += ")";
+               }
+               return name;
+       }
+       
+       /**
+        * The table model for the parameter selection.
+        * 
+        * [Body tube: Length]  [min]  [max]  [unit]
+        */
+       private class ParameterSelectionTableModel extends AbstractTableModel {
+               
+               private static final int PARAMETER = 0;
+               private static final int CURRENT = 1;
+               private static final int MIN = 2;
+               private static final int MAX = 3;
+               private static final int COUNT = 4;
+               
+               @Override
+               public int getColumnCount() {
+                       return COUNT;
+               }
+               
+               @Override
+               public int getRowCount() {
+                       return selectedModifiers.size();
+               }
+               
+               @Override
+               public String getColumnName(int column) {
+                       switch (column) {
+                       case PARAMETER:
+                               return trans.get("table.col.parameter");
+                       case CURRENT:
+                               return trans.get("table.col.current");
+                       case MIN:
+                               return trans.get("table.col.min");
+                       case MAX:
+                               return trans.get("table.col.max");
+                       default:
+                               throw new IndexOutOfBoundsException("column=" + column);
+                       }
+                       
+               }
+               
+               @Override
+               public Class<?> getColumnClass(int column) {
+                       switch (column) {
+                       case PARAMETER:
+                               return String.class;
+                       case CURRENT:
+                               return Double.class;
+                       case MIN:
+                               return Double.class;
+                       case MAX:
+                               return Double.class;
+                       default:
+                               throw new IndexOutOfBoundsException("column=" + column);
+                       }
+               }
+               
+               @Override
+               public Object getValueAt(int row, int column) {
+                       
+                       SimulationModifier modifier = selectedModifiers.get(row);
+                       
+                       switch (column) {
+                       case PARAMETER:
+                               return modifier.getRelatedObject().toString() + ": " + modifier.getName();
+                       case CURRENT:
+                               try {
+                                       return getModifierUnit(row).toUnit(modifier.getCurrentSIValue(getSelectedSimulation()));
+                               } catch (OptimizationException e) {
+                                       throw new BugException("Could not read current SI value from modifier " + modifier, e);
+                               }
+                       case MIN:
+                               return getModifierUnit(row).toUnit(modifier.getMinValue());
+                       case MAX:
+                               return getModifierUnit(row).toUnit(modifier.getMaxValue());
+                       default:
+                               throw new IndexOutOfBoundsException("column=" + column);
+                       }
+                       
+               }
+               
+               @Override
+               public void setValueAt(Object value, int row, int column) {
+                       
+                       switch (column) {
+                       case PARAMETER:
+                               break;
+                       
+                       case MIN:
+                               double min = (Double) value;
+                               min = getModifierUnit(row).fromUnit(min);
+                               selectedModifiers.get(row).setMinValue(min);
+                               break;
+                       
+                       case CURRENT:
+                               break;
+                       
+                       case MAX:
+                               double max = (Double) value;
+                               max = getModifierUnit(row).fromUnit(max);
+                               selectedModifiers.get(row).setMaxValue(max);
+                               break;
+                       
+                       default:
+                               throw new IndexOutOfBoundsException("column=" + column);
+                       }
+                       this.fireTableRowsUpdated(row, row);
+                       
+               }
+               
+               @Override
+               public boolean isCellEditable(int row, int column) {
+                       switch (column) {
+                       case PARAMETER:
+                               return false;
+                       case CURRENT:
+                               return false;
+                       case MIN:
+                               return true;
+                       case MAX:
+                               return true;
+                       default:
+                               throw new IndexOutOfBoundsException("column=" + column);
+                       }
+               }
+               
+       }
+       
+       
+       private class DoubleCellRenderer extends DefaultTableCellRenderer {
+               @Override
+               public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected,
+                               boolean hasFocus, int row, int column) {
+                       
+                       super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
+                       
+                       double val = (Double) value;
+                       Unit unit = getModifierUnit(row);
+                       
+                       val = unit.fromUnit(val);
+                       this.setText(unit.toStringUnit(val));
+                       
+                       return this;
+               }
+       }
+       
+       
+       private static class SimulationModifierComparator implements Comparator<SimulationModifier> {
+               
+               @Override
+               public int compare(SimulationModifier mod1, SimulationModifier mod2) {
+                       Object rel1 = mod1.getRelatedObject();
+                       Object rel2 = mod2.getRelatedObject();
+                       
+                       /*
+                        * Primarily order by related object:
+                        * 
+                        * - RocketComponents first
+                        * - Two RocketComponents are ordered based on their position in the rocket
+                        */
+                       if (!rel1.equals(rel2)) {
+                               
+                               if (rel1 instanceof RocketComponent) {
+                                       if (rel2 instanceof RocketComponent) {
+                                               
+                                               RocketComponent root = ((RocketComponent) rel1).getRoot();
+                                               for (RocketComponent c : root) {
+                                                       if (c.equals(rel1)) {
+                                                               return -1;
+                                                       }
+                                                       if (c.equals(rel2)) {
+                                                               return 1;
+                                                       }
+                                               }
+                                               
+                                               throw new BugException("Error sorting modifiers, mod1=" + mod1 + " rel1=" + rel1 +
+                                                               " mod2=" + mod2 + " rel2=" + rel2);
+                                               
+                                       } else {
+                                               return -1;
+                                       }
+                               } else {
+                                       if (rel2 instanceof RocketComponent) {
+                                               return 1;
+                                       }
+                               }
+                               
+                       }
+                       
+                       // Secondarily sort by name
+                       return collator.compare(mod1.getName(), mod2.getName());
+               }
+       }
+       
 
 
 }
diff --git a/src/net/sf/openrocket/gui/dialogs/optimization/OptimizationPlotDialog.java b/src/net/sf/openrocket/gui/dialogs/optimization/OptimizationPlotDialog.java
new file mode 100644 (file)
index 0000000..da91712
--- /dev/null
@@ -0,0 +1,356 @@
+package net.sf.openrocket.gui.dialogs.optimization;
+
+import java.awt.Color;
+import java.awt.Paint;
+import java.awt.Window;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import javax.swing.BorderFactory;
+import javax.swing.JButton;
+import javax.swing.JDialog;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+
+import net.miginfocom.swing.MigLayout;
+import net.sf.openrocket.gui.components.StyledLabel;
+import net.sf.openrocket.l10n.Translator;
+import net.sf.openrocket.logging.LogHelper;
+import net.sf.openrocket.optimization.general.Point;
+import net.sf.openrocket.optimization.rocketoptimization.OptimizableParameter;
+import net.sf.openrocket.optimization.rocketoptimization.SimulationModifier;
+import net.sf.openrocket.startup.Application;
+import net.sf.openrocket.unit.Unit;
+import net.sf.openrocket.unit.UnitGroup;
+import net.sf.openrocket.unit.Value;
+import net.sf.openrocket.util.GUIUtil;
+import net.sf.openrocket.util.LinearInterpolator;
+import net.sf.openrocket.util.MathUtil;
+
+import org.jfree.chart.ChartFactory;
+import org.jfree.chart.ChartPanel;
+import org.jfree.chart.JFreeChart;
+import org.jfree.chart.axis.AxisLocation;
+import org.jfree.chart.axis.NumberAxis;
+import org.jfree.chart.labels.CustomXYToolTipGenerator;
+import org.jfree.chart.plot.PlotOrientation;
+import org.jfree.chart.plot.XYPlot;
+import org.jfree.chart.renderer.PaintScale;
+import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer;
+import org.jfree.chart.renderer.xy.XYShapeRenderer;
+import org.jfree.chart.title.PaintScaleLegend;
+import org.jfree.data.xy.DefaultXYZDataset;
+import org.jfree.data.xy.XYSeries;
+import org.jfree.data.xy.XYSeriesCollection;
+import org.jfree.ui.RectangleEdge;
+
+/**
+ * A class that plots the path of an optimization.
+ * 
+ * @author Sampo Niskanen <sampo.niskanen@iki.fi>
+ */
+public class OptimizationPlotDialog extends JDialog {
+       private static final LogHelper log = Application.getLogger();
+       private static final Translator trans = Application.getTranslator();
+       
+
+       private static final LinearInterpolator RED = new LinearInterpolator(
+                       new double[] { 0.0, 1.0 }, new double[] { 0.0, 1.0 }
+                       );
+       private static final LinearInterpolator GREEN = new LinearInterpolator(
+                       new double[] { 0.0, 1.0 }, new double[] { 0.0, 0.0 }
+                       );
+       private static final LinearInterpolator BLUE = new LinearInterpolator(
+                       new double[] { 0.0, 1.0 }, new double[] { 1.0, 0.0 }
+                       );
+       
+       private static final Color OUT_OF_DOMAIN_COLOR = Color.BLACK;
+       
+       private static final Color PATH_COLOR = new Color(220, 0, 0);
+       
+       
+       public OptimizationPlotDialog(List<Point> path, Map<Point, FunctionEvaluationData> evaluations,
+                       List<SimulationModifier> modifiers, OptimizableParameter parameter, UnitGroup stabilityUnit, Window parent) {
+               super(parent, trans.get("title"), ModalityType.APPLICATION_MODAL);
+               
+
+               JPanel panel = new JPanel(new MigLayout("fill"));
+               
+               ChartPanel chart;
+               if (modifiers.size() == 1) {
+                       chart = create1DPlot(path, evaluations, modifiers, parameter, stabilityUnit);
+               } else if (modifiers.size() == 2) {
+                       chart = create2DPlot(path, evaluations, modifiers, parameter, stabilityUnit);
+               } else {
+                       throw new IllegalArgumentException("Invalid dimensionality, dim=" + modifiers.size());
+               }
+               chart.setBorder(BorderFactory.createLineBorder(Color.BLACK));
+               panel.add(chart, "span, grow, wrap para");
+               
+
+               JLabel label = new StyledLabel(trans.get("lbl.zoomInstructions"), -2);
+               panel.add(label, "");
+               
+
+               JButton close = new JButton(trans.get("button.close"));
+               close.addActionListener(new ActionListener() {
+                       @Override
+                       public void actionPerformed(ActionEvent e) {
+                               OptimizationPlotDialog.this.setVisible(false);
+                       }
+               });
+               panel.add(close, "right");
+               
+
+               this.add(panel);
+               
+               GUIUtil.setDisposableDialogOptions(this, close);
+       }
+       
+       
+
+       /**
+        * Create a 1D plot of the optimization path.
+        */
+       private ChartPanel create1DPlot(List<Point> path, Map<Point, FunctionEvaluationData> evaluations,
+                       List<SimulationModifier> modifiers, OptimizableParameter parameter, UnitGroup stabilityUnit) {
+               
+               SimulationModifier modX = modifiers.get(0);
+               Unit xUnit = modX.getUnitGroup().getDefaultUnit();
+               Unit yUnit = parameter.getUnitGroup().getDefaultUnit();
+               
+               // Create the optimization path (with autosort)
+               XYSeries series = new XYSeries(trans.get("plot1d.series"), true, true);
+               List<String> tooltips = new ArrayList<String>();
+               for (Point p : evaluations.keySet()) {
+                       FunctionEvaluationData data = evaluations.get(p);
+                       if (data != null) {
+                               if (data.getParameterValue() != null) {
+                                       Value[] state = data.getState();
+                                       series.add(xUnit.toUnit(state[0].getValue()), yUnit.toUnit(data.getParameterValue().getValue()));
+                                       tooltips.add(getTooltip(data, parameter));
+                               }
+                       } else {
+                               log.error("Could not find evaluation data for point " + p);
+                       }
+               }
+               
+
+               String xLabel = modX.getRelatedObject().toString() + ": " + modX.getName() + " / " + xUnit.getUnit();
+               String yLabel = parameter.getName() + " / " + yUnit.getUnit();
+               
+               JFreeChart chart = ChartFactory.createXYLineChart(
+                               trans.get("plot1d.title"),
+                               xLabel,
+                               yLabel,
+                               null,
+                               PlotOrientation.VERTICAL,
+                               false, // Legend
+                               true, // Tooltips
+                               false); // Urls
+               
+
+               XYLineAndShapeRenderer lineRenderer = new XYLineAndShapeRenderer(true, true);
+               lineRenderer.setBaseShapesVisible(true);
+               lineRenderer.setSeriesShapesFilled(0, false);
+               //lineRenderer.setSeriesShape(0, shapeRenderer.getBaseShape());
+               lineRenderer.setSeriesOutlinePaint(0, PATH_COLOR);
+               lineRenderer.setSeriesPaint(0, PATH_COLOR);
+               lineRenderer.setUseOutlinePaint(true);
+               CustomXYToolTipGenerator tooltipGenerator = new CustomXYToolTipGenerator();
+               tooltipGenerator.addToolTipSeries(tooltips);
+               lineRenderer.setBaseToolTipGenerator(tooltipGenerator);
+               
+
+               XYPlot plot = chart.getXYPlot();
+               
+               plot.setDataset(0, new XYSeriesCollection(series));
+               plot.setRenderer(lineRenderer);
+               
+
+
+               return new ChartPanel(chart);
+       }
+       
+       /**
+        * Create a 2D plot of the optimization path.
+        */
+       private ChartPanel create2DPlot(List<Point> path, Map<Point, FunctionEvaluationData> evaluations,
+                       List<SimulationModifier> modifiers, OptimizableParameter parameter, UnitGroup stabilityUnit) {
+               
+               Unit parameterUnit = parameter.getUnitGroup().getDefaultUnit();
+               
+               SimulationModifier modX = modifiers.get(0);
+               SimulationModifier modY = modifiers.get(1);
+               
+               Unit xUnit = modX.getUnitGroup().getDefaultUnit();
+               Unit yUnit = modY.getUnitGroup().getDefaultUnit();
+               
+               // Create the optimization path dataset
+               XYSeries series = new XYSeries(trans.get("plot2d.path"), false, true);
+               List<String> pathTooltips = new ArrayList<String>();
+               for (Point p : path) {
+                       FunctionEvaluationData data = evaluations.get(p);
+                       if (data != null) {
+                               Value[] state = data.getState();
+                               series.add(xUnit.toUnit(state[0].getValue()), yUnit.toUnit(state[1].getValue()));
+                               pathTooltips.add(getTooltip(data, parameter));
+                       } else {
+                               log.error("Could not find evaluation data for point " + p);
+                       }
+               }
+               
+
+               // Create evaluations dataset
+               double min = Double.POSITIVE_INFINITY;
+               double max = Double.NEGATIVE_INFINITY;
+               double[][] evals = new double[3][evaluations.size()];
+               List<String> evalTooltips = new ArrayList<String>();
+               
+               Iterator<FunctionEvaluationData> iterator = evaluations.values().iterator();
+               for (int i = 0; i < evaluations.size(); i++) {
+                       FunctionEvaluationData data = iterator.next();
+                       Value param = data.getParameterValue();
+                       double value;
+                       if (param != null) {
+                               value = parameterUnit.toUnit(data.getParameterValue().getValue());
+                       } else {
+                               value = Double.NaN;
+                       }
+                       
+                       Value[] state = data.getState();
+                       evals[0][i] = xUnit.toUnit(state[0].getValue());
+                       evals[1][i] = yUnit.toUnit(state[1].getValue());
+                       evals[2][i] = value;
+                       
+                       if (value < min) {
+                               min = value;
+                       }
+                       if (value > max) {
+                               max = value;
+                       }
+                       
+                       evalTooltips.add(getTooltip(data, parameter));
+               }
+               DefaultXYZDataset evalDataset = new DefaultXYZDataset();
+               evalDataset.addSeries(trans.get("plot2d.evals"), evals);
+               
+
+
+               String xLabel = modX.getRelatedObject().toString() + ": " + modX.getName() + " / " + xUnit.getUnit();
+               String yLabel = modY.getRelatedObject().toString() + ": " + modY.getName() + " / " + yUnit.getUnit();
+               
+               JFreeChart chart = ChartFactory.createXYLineChart(
+                               trans.get("plot2d.title"),
+                               xLabel,
+                               yLabel,
+                               null,
+                               //evalDataset,
+                               PlotOrientation.VERTICAL,
+                               true, // Legend
+                               true, // Tooltips
+                               false); // Urls
+               
+               PaintScale paintScale = new GradientScale(min, max);
+               
+               XYShapeRenderer shapeRenderer = new XYShapeRenderer();
+               shapeRenderer.setPaintScale(paintScale);
+               shapeRenderer.setUseFillPaint(true);
+               CustomXYToolTipGenerator tooltipGenerator = new CustomXYToolTipGenerator();
+               tooltipGenerator.addToolTipSeries(evalTooltips);
+               shapeRenderer.setBaseToolTipGenerator(tooltipGenerator);
+               
+
+               shapeRenderer.getLegendItem(0, 0);
+               
+
+               XYLineAndShapeRenderer lineRenderer = new XYLineAndShapeRenderer(true, true);
+               lineRenderer.setBaseShapesVisible(true);
+               lineRenderer.setSeriesShapesFilled(0, false);
+               lineRenderer.setSeriesShape(0, shapeRenderer.getBaseShape());
+               lineRenderer.setSeriesOutlinePaint(0, PATH_COLOR);
+               lineRenderer.setSeriesPaint(0, PATH_COLOR);
+               lineRenderer.setUseOutlinePaint(true);
+               tooltipGenerator = new CustomXYToolTipGenerator();
+               tooltipGenerator.addToolTipSeries(pathTooltips);
+               lineRenderer.setBaseToolTipGenerator(tooltipGenerator);
+               
+
+               XYPlot plot = chart.getXYPlot();
+               
+               plot.setDataset(0, new XYSeriesCollection(series));
+               plot.setRenderer(lineRenderer);
+               
+               plot.setDataset(1, evalDataset);
+               plot.setRenderer(1, shapeRenderer);
+               
+
+               // Add value scale
+               NumberAxis numberAxis = new NumberAxis(parameter.getName() + " / " + parameterUnit.getUnit());
+               PaintScaleLegend scale = new PaintScaleLegend(paintScale, numberAxis);
+               scale.setPosition(RectangleEdge.RIGHT);
+               scale.setMargin(4.0D, 4.0D, 40.0D, 4.0D);
+               scale.setAxisLocation(AxisLocation.BOTTOM_OR_RIGHT);
+               chart.addSubtitle(scale);
+               
+
+               return new ChartPanel(chart);
+       }
+       
+       
+
+       private String getTooltip(FunctionEvaluationData data, OptimizableParameter parameter) {
+               String ttip = "<html>";
+               if (data.getParameterValue() != null) {
+                       ttip += parameter.getName() + ": " +
+                                       parameter.getUnitGroup().getDefaultUnit().toStringUnit(data.getParameterValue().getValue());
+                       ttip += "<br>";
+               }
+               if (data.getDomainReference() != null) {
+                       ttip += trans.get("plot.ttip.stability") + " " + data.getDomainReference();
+               }
+               return ttip;
+       }
+       
+       private class GradientScale implements PaintScale {
+               
+               private final double min;
+               private final double max;
+               
+               public GradientScale(double min, double max) {
+                       this.min = min;
+                       this.max = max;
+               }
+               
+               @Override
+               public Paint getPaint(double value) {
+                       if (Double.isNaN(value)) {
+                               return OUT_OF_DOMAIN_COLOR;
+                       }
+                       
+                       value = MathUtil.map(value, min, max, 0.0, 1.0);
+                       value = MathUtil.clamp(value, 0.0, 1.0);
+                       
+                       float r = (float) RED.getValue(value);
+                       float g = (float) GREEN.getValue(value);
+                       float b = (float) BLUE.getValue(value);
+                       
+                       return new Color(r, g, b);
+               }
+               
+               @Override
+               public double getLowerBound() {
+                       return min;
+               }
+               
+               @Override
+               public double getUpperBound() {
+                       return max;
+               }
+       }
+       
+}
diff --git a/src/net/sf/openrocket/gui/dialogs/optimization/OptimizationStepData.java b/src/net/sf/openrocket/gui/dialogs/optimization/OptimizationStepData.java
new file mode 100644 (file)
index 0000000..ec1ece5
--- /dev/null
@@ -0,0 +1,52 @@
+package net.sf.openrocket.gui.dialogs.optimization;
+
+import net.sf.openrocket.optimization.general.Point;
+
+/**
+ * Value object for optimization step data.
+ * 
+ * @author Sampo Niskanen <sampo.niskanen@iki.fi>
+ */
+public class OptimizationStepData {
+       
+       private final Point oldPoint;
+       private final double oldValue;
+       private final Point newPoint;
+       private final double newValue;
+       private final double stepSize;
+       
+       
+       public OptimizationStepData(Point oldPoint, double oldValue, Point newPoint, double newValue, double stepSize) {
+               this.oldPoint = oldPoint;
+               this.oldValue = oldValue;
+               this.newPoint = newPoint;
+               this.newValue = newValue;
+               this.stepSize = stepSize;
+       }
+       
+       
+       public Point getOldPoint() {
+               return oldPoint;
+       }
+       
+       
+       public double getOldValue() {
+               return oldValue;
+       }
+       
+       
+       public Point getNewPoint() {
+               return newPoint;
+       }
+       
+       
+       public double getNewValue() {
+               return newValue;
+       }
+       
+       
+       public double getStepSize() {
+               return stepSize;
+       }
+       
+}
diff --git a/src/net/sf/openrocket/gui/dialogs/optimization/OptimizationWorker.java b/src/net/sf/openrocket/gui/dialogs/optimization/OptimizationWorker.java
new file mode 100644 (file)
index 0000000..b7d1844
--- /dev/null
@@ -0,0 +1,229 @@
+package net.sf.openrocket.gui.dialogs.optimization;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.LinkedBlockingQueue;
+
+import javax.swing.SwingUtilities;
+
+import net.sf.openrocket.document.Simulation;
+import net.sf.openrocket.logging.LogHelper;
+import net.sf.openrocket.optimization.general.FunctionOptimizer;
+import net.sf.openrocket.optimization.general.OptimizationController;
+import net.sf.openrocket.optimization.general.OptimizationException;
+import net.sf.openrocket.optimization.general.ParallelExecutorCache;
+import net.sf.openrocket.optimization.general.ParallelFunctionCache;
+import net.sf.openrocket.optimization.general.Point;
+import net.sf.openrocket.optimization.general.multidim.MultidirectionalSearchOptimizer;
+import net.sf.openrocket.optimization.rocketoptimization.OptimizableParameter;
+import net.sf.openrocket.optimization.rocketoptimization.OptimizationGoal;
+import net.sf.openrocket.optimization.rocketoptimization.RocketOptimizationFunction;
+import net.sf.openrocket.optimization.rocketoptimization.RocketOptimizationListener;
+import net.sf.openrocket.optimization.rocketoptimization.SimulationDomain;
+import net.sf.openrocket.optimization.rocketoptimization.SimulationModifier;
+import net.sf.openrocket.startup.Application;
+import net.sf.openrocket.unit.Value;
+import net.sf.openrocket.util.BugException;
+
+/**
+ * A background worker that runs the optimization in the background.  It supports providing
+ * evaluation and step counter information via listeners that are executed on the EDT.
+ * 
+ * @author Sampo Niskanen <sampo.niskanen@iki.fi>
+ */
+public abstract class OptimizationWorker extends Thread implements OptimizationController, RocketOptimizationListener {
+       
+       /*
+        * Note:  This is implemented as a separate Thread object instead of a SwingWorker because
+        * the SwingWorker cannot be interrupted in any way except by canceling the task, which
+        * makes it impossible to wait for its exiting (SwingWorker.get() throws a CancellationException
+        * if cancel() has been called).
+        * 
+        * SwingWorker also seems to miss some chunks that have been provided to process() when the
+        * thread ends.
+        * 
+        * Nothing of this is documented, of course...
+        */
+
+       private static final LogHelper log = Application.getLogger();
+       
+       /** Notify listeners every this many milliseconds */
+       private static final long PURGE_TIMEOUT = 500;
+       /** End optimization when step size is below this threshold */
+       private static final double STEP_SIZE_LIMIT = 0.005;
+       
+       private final FunctionOptimizer optimizer;
+       private final RocketOptimizationFunction function;
+       
+       private final Simulation simulation;
+       private final SimulationModifier[] modifiers;
+       
+       private final ParallelFunctionCache cache;
+       
+
+       private final LinkedBlockingQueue<FunctionEvaluationData> evaluationQueue =
+                       new LinkedBlockingQueue<FunctionEvaluationData>();
+       private final LinkedBlockingQueue<OptimizationStepData> stepQueue =
+                       new LinkedBlockingQueue<OptimizationStepData>();
+       private volatile long lastPurge = 0;
+       
+       private OptimizationException optimizationException = null;
+       
+       
+       /**
+        * Sole constructor
+        * @param simulation    the simulation
+        * @param parameter                     the optimization parameter
+        * @param goal                          the optimization goal
+        * @param domain                        the optimization domain
+        * @param modifiers                     the simulation modifiers
+        */
+       public OptimizationWorker(Simulation simulation, OptimizableParameter parameter,
+                       OptimizationGoal goal, SimulationDomain domain, SimulationModifier... modifiers) {
+               
+               this.simulation = simulation;
+               this.modifiers = modifiers.clone();
+               
+               function = new RocketOptimizationFunction(simulation, parameter, goal, domain, modifiers);
+               function.addRocketOptimizationListener(this);
+               
+               cache = new ParallelExecutorCache(1);
+               cache.setFunction(function);
+               
+               optimizer = new MultidirectionalSearchOptimizer(cache);
+       }
+       
+       
+       @Override
+       public void run() {
+               try {
+                       
+                       double[] current = new double[modifiers.length];
+                       for (int i = 0; i < modifiers.length; i++) {
+                               current[i] = modifiers[i].getCurrentScaledValue(simulation);
+                       }
+                       Point initial = new Point(current);
+                       
+                       optimizer.optimize(initial, this);
+                       
+               } catch (OptimizationException e) {
+                       this.optimizationException = e;
+               } finally {
+                       SwingUtilities.invokeLater(new Runnable() {
+                               @Override
+                               public void run() {
+                                       lastPurge = System.currentTimeMillis() + 24L * 3600L * 1000L;
+                                       processQueue();
+                                       done(optimizationException);
+                               }
+                       });
+               }
+       }
+       
+       /**
+        * This method is called after the optimization has ended, either normally, when interrupted
+        * or by throwing an exception.  This method is called on the EDT, like the done() method of SwingWorker.
+        * <p>
+        * All data chunks to the listeners will be guaranteed to have been processed before calling done().
+        * 
+        * @param exception             a possible optimization exception that occurred, or <code>null</code> for normal exit.
+        */
+       protected abstract void done(OptimizationException exception);
+       
+       
+       /**
+        * This method is called for each function evaluation that has taken place.
+        * This method is called on the EDT.
+        * 
+        * @param data  the data accumulated since the last call
+        */
+       protected abstract void functionEvaluated(List<FunctionEvaluationData> data);
+       
+       /**
+        * This method is called after each step taken by the optimization algorithm.
+        * This method is called on the EDT.
+        * 
+        * @param data  the data accumulated since the last call
+        */
+       protected abstract void optimizationStepTaken(List<OptimizationStepData> data);
+       
+       
+       /**
+        * Publishes data to the listeners.  The queue is purged every PURGE_TIMEOUT milliseconds.
+        * 
+        * @param data  the data to publish to the listeners
+        */
+       private synchronized void publish(FunctionEvaluationData evaluation, OptimizationStepData step) {
+               
+               if (evaluation != null) {
+                       evaluationQueue.add(evaluation);
+               }
+               if (step != null) {
+                       stepQueue.add(step);
+               }
+               
+               // Add a method to the EDT to process the queue data
+               long now = System.currentTimeMillis();
+               if (lastPurge + PURGE_TIMEOUT <= now) {
+                       lastPurge = now;
+                       SwingUtilities.invokeLater(new Runnable() {
+                               @Override
+                               public void run() {
+                                       processQueue();
+                               }
+                       });
+               }
+               
+       }
+       
+       
+       /**
+        * Process the queue and call the listeners.  This method must always be called from the EDT.
+        */
+       private void processQueue() {
+               
+               if (!SwingUtilities.isEventDispatchThread()) {
+                       throw new BugException("processQueue called from non-EDT");
+               }
+               
+
+               List<FunctionEvaluationData> evaluations = new ArrayList<FunctionEvaluationData>();
+               evaluationQueue.drainTo(evaluations);
+               if (!evaluations.isEmpty()) {
+                       functionEvaluated(evaluations);
+               }
+               
+
+               List<OptimizationStepData> steps = new ArrayList<OptimizationStepData>();
+               stepQueue.drainTo(steps);
+               if (!steps.isEmpty()) {
+                       optimizationStepTaken(steps);
+               }
+       }
+       
+       
+
+
+       /*
+        * NOTE:  The stepTaken and evaluated methods may be called from other
+        * threads than the EDT or the SwingWorker thread!
+        */
+
+       @Override
+       public boolean stepTaken(Point oldPoint, double oldValue, Point newPoint, double newValue, double stepSize) {
+               publish(null, new OptimizationStepData(oldPoint, oldValue, newPoint, newValue, stepSize));
+               
+               if (stepSize < STEP_SIZE_LIMIT) {
+                       log.info("stepSize=" + stepSize + " is below limit, ending optimization");
+                       return false;
+               } else {
+                       return true;
+               }
+       }
+       
+       @Override
+       public void evaluated(Point point, Value[] state, Value domainReference, Value parameterValue, double goalValue) {
+               publish(new FunctionEvaluationData(point, state, domainReference, parameterValue, goalValue), null);
+       }
+       
+}
index 73296bbd60f8d9b607da2956d352c2c73af8c4ad..1e7e47f0318fecf0c52cb52cd2cdf9fbf133552c 100644 (file)
@@ -8,8 +8,10 @@ import java.util.List;
 import java.util.Map;
 
 import javax.swing.JTree;
+import javax.swing.ToolTipManager;
 import javax.swing.tree.DefaultMutableTreeNode;
 import javax.swing.tree.DefaultTreeCellRenderer;
+import javax.swing.tree.DefaultTreeModel;
 import javax.swing.tree.TreePath;
 
 import net.sf.openrocket.gui.components.BasicTree;
@@ -17,6 +19,7 @@ import net.sf.openrocket.gui.main.ComponentIcons;
 import net.sf.openrocket.optimization.rocketoptimization.SimulationModifier;
 import net.sf.openrocket.rocketcomponent.Rocket;
 import net.sf.openrocket.rocketcomponent.RocketComponent;
+import net.sf.openrocket.util.TextUtil;
 
 /**
  * A tree that displays the simulation modifiers in a tree structure.
@@ -28,28 +31,39 @@ import net.sf.openrocket.rocketcomponent.RocketComponent;
  */
 public class SimulationModifierTree extends BasicTree {
        
+       private final List<SimulationModifier> selectedModifiers;
+       
        /**
         * Sole constructor.
         * 
         * @param rocket                                the rocket.
         * @param simulationModifiers   the simulation modifiers, ordered and mapped by components
+        * @param selectedModifiers             a list of the currently selected modifiers (may be modified).
         */
-       public SimulationModifierTree(Rocket rocket, Map<Object, List<SimulationModifier>> simulationModifiers) {
-               super(createModifierTree(rocket, simulationModifiers));
+       public SimulationModifierTree(Rocket rocket, Map<Object, List<SimulationModifier>> simulationModifiers,
+                       List<SimulationModifier> selectedModifiers) {
+               this.selectedModifiers = selectedModifiers;
                
+               populateTree(rocket, simulationModifiers);
                this.setCellRenderer(new ComponentModifierTreeRenderer());
+               
+               // Enable tooltips for this component
+               ToolTipManager.sharedInstance().registerComponent(this);
+               
                expandComponents();
        }
        
        
-
-       private static DefaultMutableTreeNode createModifierTree(Rocket rocket,
-                       Map<Object, List<SimulationModifier>> simulationModifiers) {
+       /**
+        * Populate the simulation modifier tree from the provided information.  This can be used to update
+        * the tree.
+        */
+       public void populateTree(Rocket rocket, Map<Object, List<SimulationModifier>> simulationModifiers) {
                
                DefaultMutableTreeNode baseNode = new DefaultMutableTreeNode(rocket);
                populateTree(baseNode, rocket, simulationModifiers);
                
-               return baseNode;
+               this.setModel(new DefaultTreeModel(baseNode));
        }
        
        
@@ -83,8 +97,11 @@ public class SimulationModifierTree extends BasicTree {
        }
        
        
+       /**
+        * Expand the rocket components, but not the modifiers.
+        */
        @SuppressWarnings("rawtypes")
-       private void expandComponents() {
+       public void expandComponents() {
                DefaultMutableTreeNode baseNode = (DefaultMutableTreeNode) this.getModel().getRoot();
                
                Enumeration enumeration = baseNode.breadthFirstEnumeration();
@@ -135,13 +152,30 @@ public class SimulationModifierTree extends BasicTree {
                        if (object instanceof RocketComponent) {
                                setForeground(Color.GRAY);
                                setFont(componentFont);
+                               
+                               // Set tooltip
+                               RocketComponent c = (RocketComponent) object;
+                               String comment = c.getComment().trim();
+                               if (comment.length() > 0) {
+                                       comment = TextUtil.htmlEncode(comment);
+                                       comment = "<html>" + comment.replace("\n", "<br>");
+                                       this.setToolTipText(comment);
+                               } else {
+                                       this.setToolTipText(null);
+                               }
                        } else if (object instanceof String) {
                                setForeground(Color.GRAY);
                                setFont(stringFont);
                        } else if (object instanceof SimulationModifier) {
-                               setForeground(Color.BLACK);
+                               
+                               if (selectedModifiers.contains(object)) {
+                                       setForeground(Color.GRAY);
+                               } else {
+                                       setForeground(Color.BLACK);
+                               }
                                setFont(modifierFont);
                                setText(((SimulationModifier) object).getName());
+                               setToolTipText(((SimulationModifier) object).getDescription());
                        }
                        
                        return this;
index a9cbbfc0b00fdb3d466d595ba9df66d234997658..e4b2040821d17782b5cc27b6bbe18933a2be6c7b 100644 (file)
@@ -30,6 +30,7 @@ import java.util.List;
 import java.util.concurrent.ExecutionException;
 
 import javax.swing.Action;
+import javax.swing.BorderFactory;
 import javax.swing.InputMap;
 import javax.swing.JButton;
 import javax.swing.JComponent;
@@ -80,7 +81,6 @@ import net.sf.openrocket.gui.dialogs.WarningDialog;
 import net.sf.openrocket.gui.dialogs.optimization.GeneralOptimizationDialog;
 import net.sf.openrocket.gui.dialogs.preferences.PreferencesDialog;
 import net.sf.openrocket.gui.main.componenttree.ComponentTree;
-import net.sf.openrocket.gui.optimization.OptimizationTestDialog;
 import net.sf.openrocket.gui.scalefigure.RocketPanel;
 import net.sf.openrocket.l10n.Translator;
 import net.sf.openrocket.logging.LogHelper;
@@ -367,8 +367,8 @@ public class BasicFrame extends JFrame {
                scroll.setBorder(null);
                scroll.setViewportBorder(null);
                
-               TitledBorder border = new TitledBorder(trans.get("BasicFrame.title.Addnewcomp"));
-               border.setTitleFont(border.getTitleFont().deriveFont(Font.BOLD));
+               TitledBorder border = BorderFactory.createTitledBorder(trans.get("BasicFrame.title.Addnewcomp"));
+               GUIUtil.changeFontStyle(border, Font.BOLD);
                scroll.setBorder(border);
                
                panel.add(scroll, "grow");
@@ -648,6 +648,19 @@ public class BasicFrame extends JFrame {
                menu.add(item);
                
 
+               item = new JMenuItem(trans.get("main.menu.analyze.optimization"), KeyEvent.VK_O);
+               item.getAccessibleContext().setAccessibleDescription(trans.get("main.menu.analyze.optimization.desc"));
+               item.addActionListener(new ActionListener() {
+                       @Override
+                       public void actionPerformed(ActionEvent e) {
+                               log.user("Rocket optimization selected");
+                               new GeneralOptimizationDialog(document, BasicFrame.this).setVisible(true);
+                       }
+               });
+               menu.add(item);
+               
+
+
                ////  Debug
                // (shown if openrocket.debug.menu is defined)
                if (System.getProperty("openrocket.debug.menu") != null) {
@@ -951,25 +964,6 @@ public class BasicFrame extends JFrame {
                menu.add(item);
                
 
-               item = new JMenuItem("General optimization test");
-               item.addActionListener(new ActionListener() {
-                       @Override
-                       public void actionPerformed(ActionEvent e) {
-                               new GeneralOptimizationDialog(document, BasicFrame.this).setVisible(true);
-                       }
-               });
-               menu.add(item);
-               
-
-               item = new JMenuItem("Optimization test");
-               item.addActionListener(new ActionListener() {
-                       @Override
-                       public void actionPerformed(ActionEvent e) {
-                               new OptimizationTestDialog(BasicFrame.this, document).setVisible(true);
-                       }
-               });
-               menu.add(item);
-               
                return menu;
        }
        
@@ -1370,13 +1364,6 @@ public class BasicFrame extends JFrame {
         * 
         */
        public void printAction() {
-               if (!Prefs.getBoolean("printing.experimental.communicated", false)) {
-                       log.info("Showing printing is experimental warning to the user");
-                       JOptionPane.showMessageDialog(this, "Printing is an currently an experimental feature " +
-                                       "and might not fully work on all platforms",
-                                       "Experimental feature", JOptionPane.WARNING_MESSAGE);
-                       Prefs.putBoolean("printing.experimental.communicated", true);
-               }
                new PrintDialog(this, document).setVisible(true);
        }
        
index 501f79b00e7f47faad4ffdd81eda7dffcaf89c0d..90561b49c96079305f0f2f9c94e1e115496c1281 100644 (file)
@@ -48,7 +48,7 @@ import net.sf.openrocket.rocketcomponent.Configuration;
 import net.sf.openrocket.simulation.FlightData;
 import net.sf.openrocket.simulation.FlightDataBranch;
 import net.sf.openrocket.simulation.RK4SimulationStepper;
-import net.sf.openrocket.simulation.GUISimulationConditions;
+import net.sf.openrocket.simulation.SimulationOptions;
 import net.sf.openrocket.simulation.FlightDataType;
 import net.sf.openrocket.simulation.listeners.SimulationListener;
 import net.sf.openrocket.simulation.listeners.example.CSVSaveListener;
@@ -81,7 +81,7 @@ public class SimulationEditDialog extends JDialog {
        
        private final Window parentWindow;
        private final Simulation simulation;
-       private final GUISimulationConditions conditions;
+       private final SimulationOptions conditions;
        private final Configuration configuration;
        private static final Translator trans = Application.getTranslator();
 
@@ -96,7 +96,7 @@ public class SimulationEditDialog extends JDialog {
                
                this.parentWindow = parent;
                this.simulation = s;
-               this.conditions = simulation.getConditions();
+               this.conditions = simulation.getOptions();
                configuration = simulation.getConfiguration();
                
                JPanel mainPanel = new JPanel(new MigLayout("fill","[grow, fill]"));
@@ -496,7 +496,7 @@ public class SimulationEditDialog extends JDialog {
                sub.add(label);
                
                m = new DoubleModel(conditions,"LaunchRodAngle", UnitGroup.UNITS_ANGLE,
-                               0, GUISimulationConditions.MAX_LAUNCH_ROD_ANGLE);
+                               0, SimulationOptions.MAX_LAUNCH_ROD_ANGLE);
                
                spin = new JSpinner(m.getSpinnerModel());
                spin.setEditor(new SpinnerEditor(spin));
@@ -507,7 +507,7 @@ public class SimulationEditDialog extends JDialog {
                unit.setToolTipText(tip);
                sub.add(unit,"growx");
                slider = new BasicSlider(m.getSliderModel(0, Math.PI/9, 
-                               GUISimulationConditions.MAX_LAUNCH_ROD_ANGLE));
+                               SimulationOptions.MAX_LAUNCH_ROD_ANGLE));
                slider.setToolTipText(tip);
                sub.add(slider,"w 75lp, wrap");
                
index 96693eb69730501ff264ac41a71c13d29c9b0fc8..1788515e78f62b0a16beb18e43d9145cd4994d8b 100644 (file)
@@ -48,8 +48,8 @@ import net.sf.openrocket.util.Prefs;
 public class SimulationRunDialog extends JDialog {
        private static final LogHelper log = Application.getLogger();
        private static final Translator trans = Application.getTranslator();
-
        
+
        /** Update the dialog status every this many ms */
        private static final long UPDATE_MS = 200;
        
@@ -95,6 +95,12 @@ public class SimulationRunDialog extends JDialog {
                
                this.simulations = simulations;
                
+
+               // Randomize the simulation random seeds
+               for (Simulation sim : simulations) {
+                       sim.getOptions().randomizeSeed();
+               }
+               
                // Initialize the simulations
                int n = simulations.length;
                simulationNames = new String[n];
@@ -127,7 +133,7 @@ public class SimulationRunDialog extends JDialog {
                panel.add(altLabel, "growx, wrap rel");
                
                //// Velocity:
-               panel.add(new JLabel(trans.get("SimuRunDlg.lbl.Velocity") +" "));
+               panel.add(new JLabel(trans.get("SimuRunDlg.lbl.Velocity") + " "));
                velLabel = new JLabel("");
                panel.add(velLabel, "growx, wrap para");
                
@@ -272,7 +278,7 @@ public class SimulationRunDialog extends JDialog {
                        double launchBurn = 0;
                        double otherBurn = 0;
                        Configuration config = simulation.getConfiguration();
-                       String id = simulation.getConditions().getMotorConfigurationID();
+                       String id = simulation.getOptions().getMotorConfigurationID();
                        Iterator<MotorMount> iterator = config.motorIterator();
                        while (iterator.hasNext()) {
                                MotorMount m = iterator.next();
@@ -410,7 +416,7 @@ public class SimulationRunDialog extends JDialog {
                                DetailDialog.showDetailedMessageDialog(SimulationRunDialog.this,
                                                new Object[] {
                                                                //// An exception occurred during the simulation:
-                                               trans.get("SimuRunDlg.msg.AnException1"),
+                                                               trans.get("SimuRunDlg.msg.AnException1"),
                                                                t.getMessage(),
                                                                simulation.getSimulationListeners().isEmpty() ?
                                                                                trans.get("SimuRunDlg.msg.AnException2") : ""
@@ -422,10 +428,10 @@ public class SimulationRunDialog extends JDialog {
                                t.printStackTrace();
                                DetailDialog.showDetailedMessageDialog(SimulationRunDialog.this,
                                                new Object[] {
-                                               //// A computation error occurred during the simulation.
-                                               trans.get("SimuRunDlg.msg.AssertionError1"),
-                                               //// Please report this as a bug along with the details below.
-                                               trans.get("SimuRunDlg.msg.AssertionError2")
+                                                               //// A computation error occurred during the simulation.
+                                                               trans.get("SimuRunDlg.msg.AssertionError1"),
+                                                               //// Please report this as a bug along with the details below.
+                                                               trans.get("SimuRunDlg.msg.AssertionError2")
                                                },
                                                stackTrace, simulation.getName(), JOptionPane.ERROR_MESSAGE);
                                
@@ -435,9 +441,9 @@ public class SimulationRunDialog extends JDialog {
                                DetailDialog.showDetailedMessageDialog(SimulationRunDialog.this,
                                                new Object[] {
                                                                //// An unknown error was encountered during the simulation.
-                                               trans.get("SimuRunDlg.msg.unknownerror1"),
+                                                               trans.get("SimuRunDlg.msg.unknownerror1"),
                                                                //// The program may be unstable, you should save all your designs and restart OpenRocket now!
-                                               trans.get("SimuRunDlg.msg.unknownerror2")
+                                                               trans.get("SimuRunDlg.msg.unknownerror2")
                                                },
                                                stackTrace, simulation.getName(), JOptionPane.ERROR_MESSAGE);
                                
diff --git a/src/net/sf/openrocket/gui/optimization/OptimizationTestDialog.java b/src/net/sf/openrocket/gui/optimization/OptimizationTestDialog.java
deleted file mode 100644 (file)
index 337b971..0000000
+++ /dev/null
@@ -1,218 +0,0 @@
-package net.sf.openrocket.gui.optimization;
-
-import static net.sf.openrocket.util.MathUtil.pow2;
-
-import java.awt.Window;
-import java.awt.event.ActionEvent;
-import java.awt.event.ActionListener;
-
-import javax.swing.JButton;
-import javax.swing.JDialog;
-import javax.swing.JPanel;
-
-import net.miginfocom.swing.MigLayout;
-import net.sf.openrocket.document.OpenRocketDocument;
-import net.sf.openrocket.document.Simulation;
-import net.sf.openrocket.optimization.general.Function;
-import net.sf.openrocket.optimization.general.FunctionOptimizer;
-import net.sf.openrocket.optimization.general.OptimizationController;
-import net.sf.openrocket.optimization.general.OptimizationException;
-import net.sf.openrocket.optimization.general.ParallelExecutorCache;
-import net.sf.openrocket.optimization.general.ParallelFunctionCache;
-import net.sf.openrocket.optimization.general.Point;
-import net.sf.openrocket.optimization.general.multidim.MultidirectionalSearchOptimizer;
-import net.sf.openrocket.optimization.rocketoptimization.RocketOptimizationFunction;
-import net.sf.openrocket.optimization.rocketoptimization.SimulationDomain;
-import net.sf.openrocket.optimization.rocketoptimization.SimulationModifier;
-import net.sf.openrocket.optimization.rocketoptimization.domains.StabilityDomain;
-import net.sf.openrocket.optimization.rocketoptimization.goals.MaximizationGoal;
-import net.sf.openrocket.optimization.rocketoptimization.modifiers.GenericComponentModifier;
-import net.sf.openrocket.optimization.rocketoptimization.parameters.MaximumAltitudeParameter;
-import net.sf.openrocket.rocketcomponent.BodyTube;
-import net.sf.openrocket.rocketcomponent.Rocket;
-import net.sf.openrocket.rocketcomponent.RocketComponent;
-import net.sf.openrocket.unit.UnitGroup;
-import net.sf.openrocket.util.GUIUtil;
-
-public class OptimizationTestDialog extends JDialog {
-       
-       private final OpenRocketDocument document;
-       
-       public OptimizationTestDialog(Window parent, OpenRocketDocument document) {
-               super(parent, "Optimization", ModalityType.APPLICATION_MODAL);
-               
-               this.document = document;
-               
-               JPanel panel = new JPanel(new MigLayout("fill"));
-               this.add(panel);
-               
-               JButton button = new JButton("Test optimize");
-               button.addActionListener(new ActionListener() {
-                       @Override
-                       public void actionPerformed(ActionEvent e) {
-                               try {
-                                       doOptimize();
-                               } catch (OptimizationException e1) {
-                                       e1.printStackTrace();
-                               }
-                       }
-               });
-               panel.add(button, "wrap para");
-               
-
-
-               JButton close = new JButton("Close");
-               close.addActionListener(new ActionListener() {
-                       @Override
-                       public void actionPerformed(ActionEvent e) {
-                               OptimizationTestDialog.this.dispose();
-                       }
-               });
-               panel.add(close);
-               
-
-               GUIUtil.setDisposableDialogOptions(this, close);
-       }
-       
-       
-       private void doOptimize() throws OptimizationException {
-               Simulation sim = document.getSimulation(0);
-               Rocket rocket = sim.getRocket();
-               
-               RocketComponent body = null;
-               
-               for (RocketComponent c : rocket) {
-                       if (c instanceof BodyTube) {
-                               body = c;
-                               break;
-                       }
-               }
-               
-               Point initial;
-               
-               SimulationDomain domain;
-               //              domain= new IdentitySimulationDomain();
-               domain = new StabilityDomain(2, false);
-               
-               SimulationModifier mod1 = new GenericComponentModifier("Test", body,
-                               UnitGroup.UNITS_LENGTH, 1.0, BodyTube.class, body.getID(), "Length");
-               mod1.setMinValue(0.1);
-               mod1.setMaxValue(0.7);
-               
-               SimulationModifier mod2 = new GenericComponentModifier("Test", body,
-                               UnitGroup.UNITS_LENGTH, 2.0, BodyTube.class, body.getID(), "OuterRadius");
-               mod2.setMinValue(0.01);
-               mod2.setMaxValue(0.10);
-               
-               OptimizationController controller = new OptimizationController() {
-                       int step = 0;
-                       
-                       @Override
-                       public boolean stepTaken(Point oldPoint, double oldValue, Point newPoint, double newValue, double stepSize) {
-                               step++;
-                               System.out.println("STEP " + step + " oldValue=" + oldValue + " newValue=" + newValue +
-                                               " oldPoint=" + oldPoint + " newPoint=" + newPoint +
-                                               " stepSize=" + stepSize);
-                               return step < 20;
-                       }
-               };
-               
-
-               initial = new Point(mod1.getCurrentScaledValue(sim), mod2.getCurrentScaledValue(sim));
-               
-
-               Function function = new RocketOptimizationFunction(sim, new MaximumAltitudeParameter(),
-                               new MaximizationGoal(), domain, mod1, mod2);
-               /*
-               function = new Function() {
-                       @Override
-                       public double evaluate(Point point) throws InterruptedException, OptimizationException {
-                               // y = ax^2 + bx + c
-                               // y' = 2ax + b
-                               // 2a * pi/4 + b = 0
-                               // b = -a*pi/2
-                               // a=-1 -> b = pi/2
-                               
-
-                               double x = point.get(0);
-                               double y = -x * x + Math.PI / 2 * x;
-                               System.out.println("Evaluating at x=" + x + " value=" + y);
-                               return y;
-                       }
-               };
-               */
-
-               ParallelFunctionCache cache = new ParallelExecutorCache(1);
-               cache.setFunction(function);
-               
-               FunctionOptimizer optimizer = new MultidirectionalSearchOptimizer(cache);
-               
-               optimizer.optimize(initial, controller);
-       }
-       
-       
-       ////////////////////////////////////////////////////////////////////////////////////////////////////////
-       
-
-       private static int evalCount = 0;
-       
-       public static void main(String[] args) throws OptimizationException {
-               Point initial;
-               
-
-
-               OptimizationController controller = new OptimizationController() {
-                       int step = 0;
-                       
-                       @Override
-                       public boolean stepTaken(Point oldPoint, double oldValue, Point newPoint, double newValue, double stepSize) {
-                               step++;
-                               System.out.println("STEP " + step + " oldValue=" + oldValue + " newValue=" + newValue +
-                                               " oldPoint=" + oldPoint + " newPoint=" + newPoint +
-                                               " stepSize=" + stepSize);
-                               return step < 20;
-                       }
-               };
-               
-
-               initial = new Point(0.5, 0.5);
-               
-
-               Function function = new Function() {
-                       @Override
-                       public double evaluate(Point point) throws InterruptedException, OptimizationException {
-                               // y = ax^2 + bx + c
-                               // y' = 2ax + b
-                               // 2a * pi/4 + b = 0
-                               // b = -a*pi/2
-                               // a=-1 -> b = pi/2
-                               
-                               evalCount++;
-                               
-                               //                              double x = point.get(0);
-                               //                              double y = x * x - Math.PI / 2 * x;
-                               //                              System.out.println("Evaluating at x=" + x + " value=" + y);
-                               //                              return y;
-                               
-                               double x = point.get(0);
-                               double y = point.get(1);
-                               double z = 4 * pow2((x - 0.3231)) + 2 * pow2(y - 0.8923);
-                               
-                               System.out.println("Evaluation " + evalCount + ":  x=" + x + " y=" + y + " z=" + z);
-                               
-                               return z;
-                       }
-               };
-               
-
-               ParallelFunctionCache cache = new ParallelExecutorCache();
-               cache.setFunction(function);
-               
-               FunctionOptimizer optimizer = new MultidirectionalSearchOptimizer(cache);
-               
-               optimizer.optimize(initial, controller);
-               
-
-               System.out.println("Total evaluation count: " + evalCount);
-       }
-}
index 931227364cb36b66f776411ac8479bcd72e66c45..bd58782f70b9de073c18412ea3bb42ec319ca65f 100644 (file)
@@ -19,7 +19,7 @@ import net.sf.openrocket.util.Pair;
 public class PlotConfiguration implements Cloneable {
        
        private static final Translator trans = Application.getTranslator();
-
+       
        public static final PlotConfiguration[] DEFAULT_CONFIGURATIONS;
        static {
                ArrayList<PlotConfiguration> configs = new ArrayList<PlotConfiguration>();
@@ -676,7 +676,7 @@ public class PlotConfiguration implements Cloneable {
                                continue;
                        
                        double d = (max - min) / axis.getRangeLength();
-                       d = Math.sqrt(d); // Prioritize small ranges
+                       d = MathUtil.safeSqrt(d); // Prioritize small ranges
                        goodness += d * 100.0;
                }
                
diff --git a/src/net/sf/openrocket/gui/plot/PlotDialog.java b/src/net/sf/openrocket/gui/plot/PlotDialog.java
deleted file mode 100644 (file)
index da77548..0000000
+++ /dev/null
@@ -1,577 +0,0 @@
-package net.sf.openrocket.gui.plot;
-
-import java.awt.AlphaComposite;
-import java.awt.BasicStroke;
-import java.awt.Color;
-import java.awt.Composite;
-import java.awt.Font;
-import java.awt.Graphics2D;
-import java.awt.Image;
-import java.awt.Window;
-import java.awt.event.ActionEvent;
-import java.awt.event.ActionListener;
-import java.awt.geom.Line2D;
-import java.awt.geom.Point2D;
-import java.awt.geom.Rectangle2D;
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-
-import javax.imageio.ImageIO;
-import javax.swing.BorderFactory;
-import javax.swing.JButton;
-import javax.swing.JCheckBox;
-import javax.swing.JDialog;
-import javax.swing.JLabel;
-import javax.swing.JPanel;
-
-import net.miginfocom.swing.MigLayout;
-import net.sf.openrocket.document.Simulation;
-import net.sf.openrocket.gui.components.StyledLabel;
-import net.sf.openrocket.l10n.Translator;
-import net.sf.openrocket.simulation.FlightDataBranch;
-import net.sf.openrocket.simulation.FlightDataType;
-import net.sf.openrocket.simulation.FlightEvent;
-import net.sf.openrocket.startup.Application;
-import net.sf.openrocket.unit.Unit;
-import net.sf.openrocket.unit.UnitGroup;
-import net.sf.openrocket.util.BugException;
-import net.sf.openrocket.util.GUIUtil;
-import net.sf.openrocket.util.MathUtil;
-import net.sf.openrocket.util.Prefs;
-
-import org.jfree.chart.ChartFactory;
-import org.jfree.chart.ChartPanel;
-import org.jfree.chart.JFreeChart;
-import org.jfree.chart.annotations.XYImageAnnotation;
-import org.jfree.chart.axis.NumberAxis;
-import org.jfree.chart.axis.ValueAxis;
-import org.jfree.chart.plot.Marker;
-import org.jfree.chart.plot.PlotOrientation;
-import org.jfree.chart.plot.ValueMarker;
-import org.jfree.chart.plot.XYPlot;
-import org.jfree.chart.renderer.xy.StandardXYItemRenderer;
-import org.jfree.chart.title.TextTitle;
-import org.jfree.data.Range;
-import org.jfree.data.xy.XYSeries;
-import org.jfree.data.xy.XYSeriesCollection;
-import org.jfree.text.TextUtilities;
-import org.jfree.ui.LengthAdjustmentType;
-import org.jfree.ui.RectangleAnchor;
-import org.jfree.ui.TextAnchor;
-
-public class PlotDialog extends JDialog {
-       
-       private static final float PLOT_STROKE_WIDTH = 1.5f;
-       private static final Translator trans = Application.getTranslator();
-       
-       private static final Color DEFAULT_EVENT_COLOR = new Color(0, 0, 0);
-       private static final Map<FlightEvent.Type, Color> EVENT_COLORS =
-                       new HashMap<FlightEvent.Type, Color>();
-       static {
-               EVENT_COLORS.put(FlightEvent.Type.LAUNCH, new Color(255, 0, 0));
-               EVENT_COLORS.put(FlightEvent.Type.LIFTOFF, new Color(0, 80, 196));
-               EVENT_COLORS.put(FlightEvent.Type.LAUNCHROD, new Color(0, 100, 80));
-               EVENT_COLORS.put(FlightEvent.Type.IGNITION, new Color(230, 130, 15));
-               EVENT_COLORS.put(FlightEvent.Type.BURNOUT, new Color(80, 55, 40));
-               EVENT_COLORS.put(FlightEvent.Type.EJECTION_CHARGE, new Color(80, 55, 40));
-               EVENT_COLORS.put(FlightEvent.Type.STAGE_SEPARATION, new Color(80, 55, 40));
-               EVENT_COLORS.put(FlightEvent.Type.APOGEE, new Color(15, 120, 15));
-               EVENT_COLORS.put(FlightEvent.Type.RECOVERY_DEVICE_DEPLOYMENT, new Color(0, 0, 128));
-               EVENT_COLORS.put(FlightEvent.Type.GROUND_HIT, new Color(0, 0, 0));
-               EVENT_COLORS.put(FlightEvent.Type.SIMULATION_END, new Color(128, 0, 0));
-       }
-       
-       private static final Map<FlightEvent.Type, Image> EVENT_IMAGES =
-                       new HashMap<FlightEvent.Type, Image>();
-       static {
-               loadImage(FlightEvent.Type.LAUNCH, "pix/eventicons/event-launch.png");
-               loadImage(FlightEvent.Type.LIFTOFF, "pix/eventicons/event-liftoff.png");
-               loadImage(FlightEvent.Type.LAUNCHROD, "pix/eventicons/event-launchrod.png");
-               loadImage(FlightEvent.Type.IGNITION, "pix/eventicons/event-ignition.png");
-               loadImage(FlightEvent.Type.BURNOUT, "pix/eventicons/event-burnout.png");
-               loadImage(FlightEvent.Type.EJECTION_CHARGE, "pix/eventicons/event-ejection-charge.png");
-               loadImage(FlightEvent.Type.STAGE_SEPARATION,
-                               "pix/eventicons/event-stage-separation.png");
-               loadImage(FlightEvent.Type.APOGEE, "pix/eventicons/event-apogee.png");
-               loadImage(FlightEvent.Type.RECOVERY_DEVICE_DEPLOYMENT,
-                               "pix/eventicons/event-recovery-device-deployment.png");
-               loadImage(FlightEvent.Type.GROUND_HIT, "pix/eventicons/event-ground-hit.png");
-               loadImage(FlightEvent.Type.SIMULATION_END, "pix/eventicons/event-simulation-end.png");
-       }
-       
-       private static void loadImage(FlightEvent.Type type, String file) {
-               InputStream is;
-               
-               is = ClassLoader.getSystemResourceAsStream(file);
-               if (is == null) {
-                       System.out.println("ERROR: File " + file + " not found!");
-                       return;
-               }
-               
-               try {
-                       Image image = ImageIO.read(is);
-                       EVENT_IMAGES.put(type, image);
-               } catch (IOException ignore) {
-                       ignore.printStackTrace();
-               }
-       }
-       
-       
-
-
-       private final List<ModifiedXYItemRenderer> renderers =
-                       new ArrayList<ModifiedXYItemRenderer>();
-       
-       private PlotDialog(Window parent, Simulation simulation, PlotConfiguration config) {
-               //// Flight data plot
-               super(parent, trans.get("PlotDialog.title.Flightdataplot"));
-               this.setModalityType(ModalityType.DOCUMENT_MODAL);
-               
-               final boolean initialShowPoints = Prefs.getBoolean(Prefs.PLOT_SHOW_POINTS, false);
-               
-
-               // Fill the auto-selections
-               FlightDataBranch branch = simulation.getSimulatedData().getBranch(0);
-               PlotConfiguration filled = config.fillAutoAxes(branch);
-               List<Axis> axes = filled.getAllAxes();
-               
-
-               // Create the data series for both axes
-               XYSeriesCollection[] data = new XYSeriesCollection[2];
-               data[0] = new XYSeriesCollection();
-               data[1] = new XYSeriesCollection();
-               
-
-               // Get the domain axis type
-               final FlightDataType domainType = filled.getDomainAxisType();
-               final Unit domainUnit = filled.getDomainAxisUnit();
-               if (domainType == null) {
-                       throw new IllegalArgumentException("Domain axis type not specified.");
-               }
-               List<Double> x = branch.get(domainType);
-               
-
-               // Get plot length (ignore trailing NaN's)
-               int typeCount = filled.getTypeCount();
-               int dataLength = 0;
-               for (int i = 0; i < typeCount; i++) {
-                       FlightDataType type = filled.getType(i);
-                       List<Double> y = branch.get(type);
-                       
-                       for (int j = dataLength; j < y.size(); j++) {
-                               if (!Double.isNaN(y.get(j)) && !Double.isInfinite(y.get(j)))
-                                       dataLength = j;
-                       }
-               }
-               dataLength = Math.min(dataLength, x.size());
-               
-
-               // Create the XYSeries objects from the flight data and store into the collections
-               String[] axisLabel = new String[2];
-               for (int i = 0; i < typeCount; i++) {
-                       // Get info
-                       FlightDataType type = filled.getType(i);
-                       Unit unit = filled.getUnit(i);
-                       int axis = filled.getAxis(i);
-                       String name = getLabel(type, unit);
-                       
-                       // Store data in provided units
-                       List<Double> y = branch.get(type);
-                       XYSeries series = new XYSeries(name, false, true);
-                       for (int j = 0; j < dataLength; j++) {
-                               series.add(domainUnit.toUnit(x.get(j)), unit.toUnit(y.get(j)));
-                       }
-                       data[axis].addSeries(series);
-                       
-                       // Update axis label
-                       if (axisLabel[axis] == null)
-                               axisLabel[axis] = type.getName();
-                       else
-                               axisLabel[axis] += "; " + type.getName();
-               }
-               
-
-               // Create the chart using the factory to get all default settings
-               JFreeChart chart = ChartFactory.createXYLineChart(
-                               //// Simulated flight
-                               trans.get("PlotDialog.Chart.Simulatedflight"),
-                               null,
-                               null,
-                               null,
-                               PlotOrientation.VERTICAL,
-                               true,
-                               true,
-                               false
-                               );
-               
-               chart.addSubtitle(new TextTitle(config.getName()));
-               
-               // Add the data and formatting to the plot
-               XYPlot plot = chart.getXYPlot();
-               int axisno = 0;
-               for (int i = 0; i < 2; i++) {
-                       // Check whether axis has any data
-                       if (data[i].getSeriesCount() > 0) {
-                               // Create and set axis
-                               double min = axes.get(i).getMinValue();
-                               double max = axes.get(i).getMaxValue();
-                               NumberAxis axis = new PresetNumberAxis(min, max);
-                               axis.setLabel(axisLabel[i]);
-                               //                              axis.setRange(axes.get(i).getMinValue(), axes.get(i).getMaxValue());
-                               plot.setRangeAxis(axisno, axis);
-                               
-                               // Add data and map to the axis
-                               plot.setDataset(axisno, data[i]);
-                               ModifiedXYItemRenderer r = new ModifiedXYItemRenderer();
-                               r.setBaseShapesVisible(initialShowPoints);
-                               r.setBaseShapesFilled(true);
-                               for (int j = 0; j < data[i].getSeriesCount(); j++) {
-                                       r.setSeriesStroke(j, new BasicStroke(PLOT_STROKE_WIDTH));
-                               }
-                               renderers.add(r);
-                               plot.setRenderer(axisno, r);
-                               plot.mapDatasetToRangeAxis(axisno, axisno);
-                               axisno++;
-                       }
-               }
-               
-               plot.getDomainAxis().setLabel(getLabel(domainType, domainUnit));
-               plot.addDomainMarker(new ValueMarker(0));
-               plot.addRangeMarker(new ValueMarker(0));
-               
-
-
-               // Create list of events to show (combine event too close to each other)
-               ArrayList<Double> timeList = new ArrayList<Double>();
-               ArrayList<String> eventList = new ArrayList<String>();
-               ArrayList<Color> colorList = new ArrayList<Color>();
-               ArrayList<Image> imageList = new ArrayList<Image>();
-               
-               HashSet<FlightEvent.Type> typeSet = new HashSet<FlightEvent.Type>();
-               
-               double prevTime = -100;
-               String text = null;
-               Color color = null;
-               Image image = null;
-               
-               List<FlightEvent> events = branch.getEvents();
-               for (int i = 0; i < events.size(); i++) {
-                       FlightEvent event = events.get(i);
-                       double t = event.getTime();
-                       FlightEvent.Type type = event.getType();
-                       
-                       if (type != FlightEvent.Type.ALTITUDE && config.isEventActive(type)) {
-                               if (Math.abs(t - prevTime) <= 0.01) {
-                                       
-                                       if (!typeSet.contains(type)) {
-                                               text = text + ", " + type.toString();
-                                               color = getEventColor(type);
-                                               image = EVENT_IMAGES.get(type);
-                                               typeSet.add(type);
-                                       }
-                                       
-                               } else {
-                                       
-                                       if (text != null) {
-                                               timeList.add(prevTime);
-                                               eventList.add(text);
-                                               colorList.add(color);
-                                               imageList.add(image);
-                                       }
-                                       prevTime = t;
-                                       text = type.toString();
-                                       color = getEventColor(type);
-                                       image = EVENT_IMAGES.get(type);
-                                       typeSet.clear();
-                                       typeSet.add(type);
-                                       
-                               }
-                       }
-               }
-               if (text != null) {
-                       timeList.add(prevTime);
-                       eventList.add(text);
-                       colorList.add(color);
-                       imageList.add(image);
-               }
-               
-
-               // Create the event markers
-               
-               if (config.getDomainAxisType() == FlightDataType.TYPE_TIME) {
-                       
-                       // Domain time is plotted as vertical markers
-                       for (int i = 0; i < eventList.size(); i++) {
-                               double t = timeList.get(i);
-                               String event = eventList.get(i);
-                               color = colorList.get(i);
-                               
-                               ValueMarker m = new ValueMarker(t);
-                               m.setLabel(event);
-                               m.setPaint(color);
-                               m.setLabelPaint(color);
-                               m.setAlpha(0.7f);
-                               plot.addDomainMarker(m);
-                       }
-                       
-               } else {
-                       
-                       // Other domains are plotted as image annotations
-                       List<Double> time = branch.get(FlightDataType.TYPE_TIME);
-                       List<Double> domain = branch.get(config.getDomainAxisType());
-                       
-                       for (int i = 0; i < eventList.size(); i++) {
-                               final double t = timeList.get(i);
-                               String event = eventList.get(i);
-                               image = imageList.get(i);
-                               
-                               if (image == null)
-                                       continue;
-                               
-                               // Calculate index and interpolation position a
-                               final double a;
-                               int tindex = Collections.binarySearch(time, t);
-                               if (tindex < 0) {
-                                       tindex = -tindex - 1;
-                               }
-                               if (tindex >= time.size()) {
-                                       // index greater than largest value in time list
-                                       tindex = time.size() - 1;
-                                       a = 0;
-                               } else if (tindex <= 0) {
-                                       // index smaller than smallest value in time list
-                                       tindex = 0;
-                                       a = 0;
-                               } else {
-                                       tindex--;
-                                       double t1 = time.get(tindex);
-                                       double t2 = time.get(tindex + 1);
-                                       
-                                       if ((t1 > t) || (t2 < t)) {
-                                               throw new BugException("t1=" + t1 + " t2=" + t2 + " t=" + t);
-                                       }
-                                       
-                                       if (MathUtil.equals(t1, t2)) {
-                                               a = 0;
-                                       } else {
-                                               a = 1 - (t - t1) / (t2 - t1);
-                                       }
-                               }
-                               
-                               double xcoord;
-                               if (a == 0) {
-                                       xcoord = domain.get(tindex);
-                               } else {
-                                       xcoord = a * domain.get(tindex) + (1 - a) * domain.get(tindex + 1);
-                               }
-                               
-                               for (int index = 0; index < config.getTypeCount(); index++) {
-                                       FlightDataType type = config.getType(index);
-                                       List<Double> range = branch.get(type);
-                                       
-                                       // Image annotations are not supported on the right-side axis
-                                       // TODO: LOW: Can this be achieved by JFreeChart?
-                                       if (filled.getAxis(index) != SimulationPlotPanel.LEFT) {
-                                               continue;
-                                       }
-                                       
-                                       double ycoord;
-                                       if (a == 0) {
-                                               ycoord = range.get(tindex);
-                                       } else {
-                                               ycoord = a * range.get(tindex) + (1 - a) * range.get(tindex + 1);
-                                       }
-                                       
-                                       // Convert units
-                                       xcoord = config.getDomainAxisUnit().toUnit(xcoord);
-                                       ycoord = config.getUnit(index).toUnit(ycoord);
-                                       
-                                       XYImageAnnotation annotation =
-                                                               new XYImageAnnotation(xcoord, ycoord, image, RectangleAnchor.CENTER);
-                                       annotation.setToolTipText(event);
-                                       plot.addAnnotation(annotation);
-                               }
-                       }
-               }
-               
-
-               // Create the dialog
-               
-               JPanel panel = new JPanel(new MigLayout("fill"));
-               this.add(panel);
-               
-               ChartPanel chartPanel = new ChartPanel(chart,
-                               false, // properties
-                               true, // save
-                               false, // print
-                               true, // zoom
-                               true); // tooltips
-               chartPanel.setMouseWheelEnabled(true);
-               chartPanel.setEnforceFileExtensions(true);
-               chartPanel.setInitialDelay(500);
-               
-               chartPanel.setBorder(BorderFactory.createLineBorder(Color.GRAY, 1));
-               
-               panel.add(chartPanel, "grow, wrap 20lp");
-               
-               //// Show data points
-               final JCheckBox check = new JCheckBox(trans.get("PlotDialog.CheckBox.Showdatapoints"));
-               check.setSelected(initialShowPoints);
-               check.addActionListener(new ActionListener() {
-                       @Override
-                       public void actionPerformed(ActionEvent e) {
-                               boolean show = check.isSelected();
-                               Prefs.putBoolean(Prefs.PLOT_SHOW_POINTS, show);
-                               for (ModifiedXYItemRenderer r : renderers) {
-                                       r.setBaseShapesVisible(show);
-                               }
-                       }
-               });
-               panel.add(check, "split, left");
-               
-
-               JLabel label = new StyledLabel(trans.get("PlotDialog.lbl.Chart"), -2);
-               panel.add(label, "gapleft para");
-               
-
-               panel.add(new JPanel(), "growx");
-               
-               //// Close button
-               JButton button = new JButton(trans.get("dlg.but.close"));
-               button.addActionListener(new ActionListener() {
-                       @Override
-                       public void actionPerformed(ActionEvent e) {
-                               PlotDialog.this.dispose();
-                       }
-               });
-               panel.add(button, "right");
-               
-               this.setLocationByPlatform(true);
-               this.pack();
-               
-               GUIUtil.setDisposableDialogOptions(this, button);
-       }
-       
-       private String getLabel(FlightDataType type, Unit unit) {
-               String name = type.getName();
-               if (unit != null && !UnitGroup.UNITS_NONE.contains(unit) &&
-                               !UnitGroup.UNITS_COEFFICIENT.contains(unit) && unit.getUnit().length() > 0)
-                       name += " (" + unit.getUnit() + ")";
-               return name;
-       }
-       
-       
-
-       private class PresetNumberAxis extends NumberAxis {
-               private final double min;
-               private final double max;
-               
-               public PresetNumberAxis(double min, double max) {
-                       this.min = min;
-                       this.max = max;
-                       autoAdjustRange();
-               }
-               
-               @Override
-               protected void autoAdjustRange() {
-                       this.setRange(min, max);
-               }
-       }
-       
-       
-       /**
-        * Static method that shows a plot with the specified parameters.
-        * 
-        * @param parent                the parent window, which will be blocked.
-        * @param simulation    the simulation to plot.
-        * @param config                the configuration of the plot.
-        */
-       public static void showPlot(Window parent, Simulation simulation, PlotConfiguration config) {
-               new PlotDialog(parent, simulation, config).setVisible(true);
-       }
-       
-       
-
-       private static Color getEventColor(FlightEvent.Type type) {
-               Color c = EVENT_COLORS.get(type);
-               if (c != null)
-                       return c;
-               return DEFAULT_EVENT_COLOR;
-       }
-       
-       
-
-
-
-       /**
-        * A modification to the standard renderer that renders the domain marker
-        * labels vertically instead of horizontally.
-        */
-       private static class ModifiedXYItemRenderer extends StandardXYItemRenderer {
-               
-               @Override
-               public void drawDomainMarker(Graphics2D g2, XYPlot plot, ValueAxis domainAxis,
-                               Marker marker, Rectangle2D dataArea) {
-                       
-                       if (!(marker instanceof ValueMarker)) {
-                               // Use parent for all others
-                               super.drawDomainMarker(g2, plot, domainAxis, marker, dataArea);
-                               return;
-                       }
-                       
-                       /*
-                        * Draw the normal marker, but with rotated text.
-                        * Copied from the overridden method.
-                        */
-                       ValueMarker vm = (ValueMarker) marker;
-                       double value = vm.getValue();
-                       Range range = domainAxis.getRange();
-                       if (!range.contains(value)) {
-                               return;
-                       }
-                       
-                       double v = domainAxis.valueToJava2D(value, dataArea, plot.getDomainAxisEdge());
-                       
-                       PlotOrientation orientation = plot.getOrientation();
-                       Line2D line = null;
-                       if (orientation == PlotOrientation.HORIZONTAL) {
-                               line = new Line2D.Double(dataArea.getMinX(), v, dataArea.getMaxX(), v);
-                       } else {
-                               line = new Line2D.Double(v, dataArea.getMinY(), v, dataArea.getMaxY());
-                       }
-                       
-                       final Composite originalComposite = g2.getComposite();
-                       g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, marker
-                                       .getAlpha()));
-                       g2.setPaint(marker.getPaint());
-                       g2.setStroke(marker.getStroke());
-                       g2.draw(line);
-                       
-                       String label = marker.getLabel();
-                       RectangleAnchor anchor = marker.getLabelAnchor();
-                       if (label != null) {
-                               Font labelFont = marker.getLabelFont();
-                               g2.setFont(labelFont);
-                               g2.setPaint(marker.getLabelPaint());
-                               Point2D coordinates = calculateDomainMarkerTextAnchorPoint(g2,
-                                               orientation, dataArea, line.getBounds2D(), marker
-                                                               .getLabelOffset(), LengthAdjustmentType.EXPAND, anchor);
-                               
-                               // Changed:
-                               TextAnchor textAnchor = TextAnchor.TOP_RIGHT;
-                               TextUtilities.drawRotatedString(label, g2, (float) coordinates.getX() + 2,
-                                               (float) coordinates.getY(), textAnchor,
-                                               -Math.PI / 2, textAnchor);
-                       }
-                       g2.setComposite(originalComposite);
-               }
-               
-       }
-       
-}
diff --git a/src/net/sf/openrocket/gui/plot/SimulationPlotDialog.java b/src/net/sf/openrocket/gui/plot/SimulationPlotDialog.java
new file mode 100644 (file)
index 0000000..c68617b
--- /dev/null
@@ -0,0 +1,582 @@
+package net.sf.openrocket.gui.plot;
+
+import java.awt.AlphaComposite;
+import java.awt.BasicStroke;
+import java.awt.Color;
+import java.awt.Composite;
+import java.awt.Font;
+import java.awt.Graphics2D;
+import java.awt.Image;
+import java.awt.Window;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.geom.Line2D;
+import java.awt.geom.Point2D;
+import java.awt.geom.Rectangle2D;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+
+import javax.imageio.ImageIO;
+import javax.swing.BorderFactory;
+import javax.swing.JButton;
+import javax.swing.JCheckBox;
+import javax.swing.JDialog;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+
+import net.miginfocom.swing.MigLayout;
+import net.sf.openrocket.document.Simulation;
+import net.sf.openrocket.gui.components.StyledLabel;
+import net.sf.openrocket.l10n.Translator;
+import net.sf.openrocket.simulation.FlightDataBranch;
+import net.sf.openrocket.simulation.FlightDataType;
+import net.sf.openrocket.simulation.FlightEvent;
+import net.sf.openrocket.startup.Application;
+import net.sf.openrocket.unit.Unit;
+import net.sf.openrocket.unit.UnitGroup;
+import net.sf.openrocket.util.BugException;
+import net.sf.openrocket.util.GUIUtil;
+import net.sf.openrocket.util.MathUtil;
+import net.sf.openrocket.util.Prefs;
+
+import org.jfree.chart.ChartFactory;
+import org.jfree.chart.ChartPanel;
+import org.jfree.chart.JFreeChart;
+import org.jfree.chart.annotations.XYImageAnnotation;
+import org.jfree.chart.axis.NumberAxis;
+import org.jfree.chart.axis.ValueAxis;
+import org.jfree.chart.plot.Marker;
+import org.jfree.chart.plot.PlotOrientation;
+import org.jfree.chart.plot.ValueMarker;
+import org.jfree.chart.plot.XYPlot;
+import org.jfree.chart.renderer.xy.StandardXYItemRenderer;
+import org.jfree.chart.title.TextTitle;
+import org.jfree.data.Range;
+import org.jfree.data.xy.XYSeries;
+import org.jfree.data.xy.XYSeriesCollection;
+import org.jfree.text.TextUtilities;
+import org.jfree.ui.LengthAdjustmentType;
+import org.jfree.ui.RectangleAnchor;
+import org.jfree.ui.TextAnchor;
+
+/**
+ * Dialog that shows a plot of a simulation results based on user options.
+ * 
+ * @author Sampo Niskanen <sampo.niskanen@iki.fi>
+ */
+public class SimulationPlotDialog extends JDialog {
+       
+       private static final float PLOT_STROKE_WIDTH = 1.5f;
+       private static final Translator trans = Application.getTranslator();
+       
+       private static final Color DEFAULT_EVENT_COLOR = new Color(0, 0, 0);
+       private static final Map<FlightEvent.Type, Color> EVENT_COLORS =
+                       new HashMap<FlightEvent.Type, Color>();
+       static {
+               EVENT_COLORS.put(FlightEvent.Type.LAUNCH, new Color(255, 0, 0));
+               EVENT_COLORS.put(FlightEvent.Type.LIFTOFF, new Color(0, 80, 196));
+               EVENT_COLORS.put(FlightEvent.Type.LAUNCHROD, new Color(0, 100, 80));
+               EVENT_COLORS.put(FlightEvent.Type.IGNITION, new Color(230, 130, 15));
+               EVENT_COLORS.put(FlightEvent.Type.BURNOUT, new Color(80, 55, 40));
+               EVENT_COLORS.put(FlightEvent.Type.EJECTION_CHARGE, new Color(80, 55, 40));
+               EVENT_COLORS.put(FlightEvent.Type.STAGE_SEPARATION, new Color(80, 55, 40));
+               EVENT_COLORS.put(FlightEvent.Type.APOGEE, new Color(15, 120, 15));
+               EVENT_COLORS.put(FlightEvent.Type.RECOVERY_DEVICE_DEPLOYMENT, new Color(0, 0, 128));
+               EVENT_COLORS.put(FlightEvent.Type.GROUND_HIT, new Color(0, 0, 0));
+               EVENT_COLORS.put(FlightEvent.Type.SIMULATION_END, new Color(128, 0, 0));
+       }
+       
+       private static final Map<FlightEvent.Type, Image> EVENT_IMAGES =
+                       new HashMap<FlightEvent.Type, Image>();
+       static {
+               loadImage(FlightEvent.Type.LAUNCH, "pix/eventicons/event-launch.png");
+               loadImage(FlightEvent.Type.LIFTOFF, "pix/eventicons/event-liftoff.png");
+               loadImage(FlightEvent.Type.LAUNCHROD, "pix/eventicons/event-launchrod.png");
+               loadImage(FlightEvent.Type.IGNITION, "pix/eventicons/event-ignition.png");
+               loadImage(FlightEvent.Type.BURNOUT, "pix/eventicons/event-burnout.png");
+               loadImage(FlightEvent.Type.EJECTION_CHARGE, "pix/eventicons/event-ejection-charge.png");
+               loadImage(FlightEvent.Type.STAGE_SEPARATION,
+                               "pix/eventicons/event-stage-separation.png");
+               loadImage(FlightEvent.Type.APOGEE, "pix/eventicons/event-apogee.png");
+               loadImage(FlightEvent.Type.RECOVERY_DEVICE_DEPLOYMENT,
+                               "pix/eventicons/event-recovery-device-deployment.png");
+               loadImage(FlightEvent.Type.GROUND_HIT, "pix/eventicons/event-ground-hit.png");
+               loadImage(FlightEvent.Type.SIMULATION_END, "pix/eventicons/event-simulation-end.png");
+       }
+       
+       private static void loadImage(FlightEvent.Type type, String file) {
+               InputStream is;
+               
+               is = ClassLoader.getSystemResourceAsStream(file);
+               if (is == null) {
+                       System.out.println("ERROR: File " + file + " not found!");
+                       return;
+               }
+               
+               try {
+                       Image image = ImageIO.read(is);
+                       EVENT_IMAGES.put(type, image);
+               } catch (IOException ignore) {
+                       ignore.printStackTrace();
+               }
+       }
+       
+       
+
+
+       private final List<ModifiedXYItemRenderer> renderers =
+                       new ArrayList<ModifiedXYItemRenderer>();
+       
+       private SimulationPlotDialog(Window parent, Simulation simulation, PlotConfiguration config) {
+               //// Flight data plot
+               super(parent, trans.get("PlotDialog.title.Flightdataplot"));
+               this.setModalityType(ModalityType.DOCUMENT_MODAL);
+               
+               final boolean initialShowPoints = Prefs.getBoolean(Prefs.PLOT_SHOW_POINTS, false);
+               
+
+               // Fill the auto-selections
+               FlightDataBranch branch = simulation.getSimulatedData().getBranch(0);
+               PlotConfiguration filled = config.fillAutoAxes(branch);
+               List<Axis> axes = filled.getAllAxes();
+               
+
+               // Create the data series for both axes
+               XYSeriesCollection[] data = new XYSeriesCollection[2];
+               data[0] = new XYSeriesCollection();
+               data[1] = new XYSeriesCollection();
+               
+
+               // Get the domain axis type
+               final FlightDataType domainType = filled.getDomainAxisType();
+               final Unit domainUnit = filled.getDomainAxisUnit();
+               if (domainType == null) {
+                       throw new IllegalArgumentException("Domain axis type not specified.");
+               }
+               List<Double> x = branch.get(domainType);
+               
+
+               // Get plot length (ignore trailing NaN's)
+               int typeCount = filled.getTypeCount();
+               int dataLength = 0;
+               for (int i = 0; i < typeCount; i++) {
+                       FlightDataType type = filled.getType(i);
+                       List<Double> y = branch.get(type);
+                       
+                       for (int j = dataLength; j < y.size(); j++) {
+                               if (!Double.isNaN(y.get(j)) && !Double.isInfinite(y.get(j)))
+                                       dataLength = j;
+                       }
+               }
+               dataLength = Math.min(dataLength, x.size());
+               
+
+               // Create the XYSeries objects from the flight data and store into the collections
+               String[] axisLabel = new String[2];
+               for (int i = 0; i < typeCount; i++) {
+                       // Get info
+                       FlightDataType type = filled.getType(i);
+                       Unit unit = filled.getUnit(i);
+                       int axis = filled.getAxis(i);
+                       String name = getLabel(type, unit);
+                       
+                       // Store data in provided units
+                       List<Double> y = branch.get(type);
+                       XYSeries series = new XYSeries(name, false, true);
+                       for (int j = 0; j < dataLength; j++) {
+                               series.add(domainUnit.toUnit(x.get(j)), unit.toUnit(y.get(j)));
+                       }
+                       data[axis].addSeries(series);
+                       
+                       // Update axis label
+                       if (axisLabel[axis] == null)
+                               axisLabel[axis] = type.getName();
+                       else
+                               axisLabel[axis] += "; " + type.getName();
+               }
+               
+
+               // Create the chart using the factory to get all default settings
+               JFreeChart chart = ChartFactory.createXYLineChart(
+                               //// Simulated flight
+                               trans.get("PlotDialog.Chart.Simulatedflight"),
+                               null,
+                               null,
+                               null,
+                               PlotOrientation.VERTICAL,
+                               true,
+                               true,
+                               false
+                               );
+               
+               chart.addSubtitle(new TextTitle(config.getName()));
+               
+               // Add the data and formatting to the plot
+               XYPlot plot = chart.getXYPlot();
+               int axisno = 0;
+               for (int i = 0; i < 2; i++) {
+                       // Check whether axis has any data
+                       if (data[i].getSeriesCount() > 0) {
+                               // Create and set axis
+                               double min = axes.get(i).getMinValue();
+                               double max = axes.get(i).getMaxValue();
+                               NumberAxis axis = new PresetNumberAxis(min, max);
+                               axis.setLabel(axisLabel[i]);
+                               //                              axis.setRange(axes.get(i).getMinValue(), axes.get(i).getMaxValue());
+                               plot.setRangeAxis(axisno, axis);
+                               
+                               // Add data and map to the axis
+                               plot.setDataset(axisno, data[i]);
+                               ModifiedXYItemRenderer r = new ModifiedXYItemRenderer();
+                               r.setBaseShapesVisible(initialShowPoints);
+                               r.setBaseShapesFilled(true);
+                               for (int j = 0; j < data[i].getSeriesCount(); j++) {
+                                       r.setSeriesStroke(j, new BasicStroke(PLOT_STROKE_WIDTH));
+                               }
+                               renderers.add(r);
+                               plot.setRenderer(axisno, r);
+                               plot.mapDatasetToRangeAxis(axisno, axisno);
+                               axisno++;
+                       }
+               }
+               
+               plot.getDomainAxis().setLabel(getLabel(domainType, domainUnit));
+               plot.addDomainMarker(new ValueMarker(0));
+               plot.addRangeMarker(new ValueMarker(0));
+               
+
+
+               // Create list of events to show (combine event too close to each other)
+               ArrayList<Double> timeList = new ArrayList<Double>();
+               ArrayList<String> eventList = new ArrayList<String>();
+               ArrayList<Color> colorList = new ArrayList<Color>();
+               ArrayList<Image> imageList = new ArrayList<Image>();
+               
+               HashSet<FlightEvent.Type> typeSet = new HashSet<FlightEvent.Type>();
+               
+               double prevTime = -100;
+               String text = null;
+               Color color = null;
+               Image image = null;
+               
+               List<FlightEvent> events = branch.getEvents();
+               for (int i = 0; i < events.size(); i++) {
+                       FlightEvent event = events.get(i);
+                       double t = event.getTime();
+                       FlightEvent.Type type = event.getType();
+                       
+                       if (type != FlightEvent.Type.ALTITUDE && config.isEventActive(type)) {
+                               if (Math.abs(t - prevTime) <= 0.01) {
+                                       
+                                       if (!typeSet.contains(type)) {
+                                               text = text + ", " + type.toString();
+                                               color = getEventColor(type);
+                                               image = EVENT_IMAGES.get(type);
+                                               typeSet.add(type);
+                                       }
+                                       
+                               } else {
+                                       
+                                       if (text != null) {
+                                               timeList.add(prevTime);
+                                               eventList.add(text);
+                                               colorList.add(color);
+                                               imageList.add(image);
+                                       }
+                                       prevTime = t;
+                                       text = type.toString();
+                                       color = getEventColor(type);
+                                       image = EVENT_IMAGES.get(type);
+                                       typeSet.clear();
+                                       typeSet.add(type);
+                                       
+                               }
+                       }
+               }
+               if (text != null) {
+                       timeList.add(prevTime);
+                       eventList.add(text);
+                       colorList.add(color);
+                       imageList.add(image);
+               }
+               
+
+               // Create the event markers
+               
+               if (config.getDomainAxisType() == FlightDataType.TYPE_TIME) {
+                       
+                       // Domain time is plotted as vertical markers
+                       for (int i = 0; i < eventList.size(); i++) {
+                               double t = timeList.get(i);
+                               String event = eventList.get(i);
+                               color = colorList.get(i);
+                               
+                               ValueMarker m = new ValueMarker(t);
+                               m.setLabel(event);
+                               m.setPaint(color);
+                               m.setLabelPaint(color);
+                               m.setAlpha(0.7f);
+                               plot.addDomainMarker(m);
+                       }
+                       
+               } else {
+                       
+                       // Other domains are plotted as image annotations
+                       List<Double> time = branch.get(FlightDataType.TYPE_TIME);
+                       List<Double> domain = branch.get(config.getDomainAxisType());
+                       
+                       for (int i = 0; i < eventList.size(); i++) {
+                               final double t = timeList.get(i);
+                               String event = eventList.get(i);
+                               image = imageList.get(i);
+                               
+                               if (image == null)
+                                       continue;
+                               
+                               // Calculate index and interpolation position a
+                               final double a;
+                               int tindex = Collections.binarySearch(time, t);
+                               if (tindex < 0) {
+                                       tindex = -tindex - 1;
+                               }
+                               if (tindex >= time.size()) {
+                                       // index greater than largest value in time list
+                                       tindex = time.size() - 1;
+                                       a = 0;
+                               } else if (tindex <= 0) {
+                                       // index smaller than smallest value in time list
+                                       tindex = 0;
+                                       a = 0;
+                               } else {
+                                       tindex--;
+                                       double t1 = time.get(tindex);
+                                       double t2 = time.get(tindex + 1);
+                                       
+                                       if ((t1 > t) || (t2 < t)) {
+                                               throw new BugException("t1=" + t1 + " t2=" + t2 + " t=" + t);
+                                       }
+                                       
+                                       if (MathUtil.equals(t1, t2)) {
+                                               a = 0;
+                                       } else {
+                                               a = 1 - (t - t1) / (t2 - t1);
+                                       }
+                               }
+                               
+                               double xcoord;
+                               if (a == 0) {
+                                       xcoord = domain.get(tindex);
+                               } else {
+                                       xcoord = a * domain.get(tindex) + (1 - a) * domain.get(tindex + 1);
+                               }
+                               
+                               for (int index = 0; index < config.getTypeCount(); index++) {
+                                       FlightDataType type = config.getType(index);
+                                       List<Double> range = branch.get(type);
+                                       
+                                       // Image annotations are not supported on the right-side axis
+                                       // TODO: LOW: Can this be achieved by JFreeChart?
+                                       if (filled.getAxis(index) != SimulationPlotPanel.LEFT) {
+                                               continue;
+                                       }
+                                       
+                                       double ycoord;
+                                       if (a == 0) {
+                                               ycoord = range.get(tindex);
+                                       } else {
+                                               ycoord = a * range.get(tindex) + (1 - a) * range.get(tindex + 1);
+                                       }
+                                       
+                                       // Convert units
+                                       xcoord = config.getDomainAxisUnit().toUnit(xcoord);
+                                       ycoord = config.getUnit(index).toUnit(ycoord);
+                                       
+                                       XYImageAnnotation annotation =
+                                                               new XYImageAnnotation(xcoord, ycoord, image, RectangleAnchor.CENTER);
+                                       annotation.setToolTipText(event);
+                                       plot.addAnnotation(annotation);
+                               }
+                       }
+               }
+               
+
+               // Create the dialog
+               
+               JPanel panel = new JPanel(new MigLayout("fill"));
+               this.add(panel);
+               
+               ChartPanel chartPanel = new ChartPanel(chart,
+                               false, // properties
+                               true, // save
+                               false, // print
+                               true, // zoom
+                               true); // tooltips
+               chartPanel.setMouseWheelEnabled(true);
+               chartPanel.setEnforceFileExtensions(true);
+               chartPanel.setInitialDelay(500);
+               
+               chartPanel.setBorder(BorderFactory.createLineBorder(Color.GRAY, 1));
+               
+               panel.add(chartPanel, "grow, wrap 20lp");
+               
+               //// Show data points
+               final JCheckBox check = new JCheckBox(trans.get("PlotDialog.CheckBox.Showdatapoints"));
+               check.setSelected(initialShowPoints);
+               check.addActionListener(new ActionListener() {
+                       @Override
+                       public void actionPerformed(ActionEvent e) {
+                               boolean show = check.isSelected();
+                               Prefs.putBoolean(Prefs.PLOT_SHOW_POINTS, show);
+                               for (ModifiedXYItemRenderer r : renderers) {
+                                       r.setBaseShapesVisible(show);
+                               }
+                       }
+               });
+               panel.add(check, "split, left");
+               
+
+               JLabel label = new StyledLabel(trans.get("PlotDialog.lbl.Chart"), -2);
+               panel.add(label, "gapleft para");
+               
+
+               panel.add(new JPanel(), "growx");
+               
+               //// Close button
+               JButton button = new JButton(trans.get("dlg.but.close"));
+               button.addActionListener(new ActionListener() {
+                       @Override
+                       public void actionPerformed(ActionEvent e) {
+                               SimulationPlotDialog.this.dispose();
+                       }
+               });
+               panel.add(button, "right");
+               
+               this.setLocationByPlatform(true);
+               this.pack();
+               
+               GUIUtil.setDisposableDialogOptions(this, button);
+       }
+       
+       private String getLabel(FlightDataType type, Unit unit) {
+               String name = type.getName();
+               if (unit != null && !UnitGroup.UNITS_NONE.contains(unit) &&
+                               !UnitGroup.UNITS_COEFFICIENT.contains(unit) && unit.getUnit().length() > 0)
+                       name += " (" + unit.getUnit() + ")";
+               return name;
+       }
+       
+       
+
+       private class PresetNumberAxis extends NumberAxis {
+               private final double min;
+               private final double max;
+               
+               public PresetNumberAxis(double min, double max) {
+                       this.min = min;
+                       this.max = max;
+                       autoAdjustRange();
+               }
+               
+               @Override
+               protected void autoAdjustRange() {
+                       this.setRange(min, max);
+               }
+       }
+       
+       
+       /**
+        * Static method that shows a plot with the specified parameters.
+        * 
+        * @param parent                the parent window, which will be blocked.
+        * @param simulation    the simulation to plot.
+        * @param config                the configuration of the plot.
+        */
+       public static void showPlot(Window parent, Simulation simulation, PlotConfiguration config) {
+               new SimulationPlotDialog(parent, simulation, config).setVisible(true);
+       }
+       
+       
+
+       private static Color getEventColor(FlightEvent.Type type) {
+               Color c = EVENT_COLORS.get(type);
+               if (c != null)
+                       return c;
+               return DEFAULT_EVENT_COLOR;
+       }
+       
+       
+
+
+
+       /**
+        * A modification to the standard renderer that renders the domain marker
+        * labels vertically instead of horizontally.
+        */
+       private static class ModifiedXYItemRenderer extends StandardXYItemRenderer {
+               
+               @Override
+               public void drawDomainMarker(Graphics2D g2, XYPlot plot, ValueAxis domainAxis,
+                               Marker marker, Rectangle2D dataArea) {
+                       
+                       if (!(marker instanceof ValueMarker)) {
+                               // Use parent for all others
+                               super.drawDomainMarker(g2, plot, domainAxis, marker, dataArea);
+                               return;
+                       }
+                       
+                       /*
+                        * Draw the normal marker, but with rotated text.
+                        * Copied from the overridden method.
+                        */
+                       ValueMarker vm = (ValueMarker) marker;
+                       double value = vm.getValue();
+                       Range range = domainAxis.getRange();
+                       if (!range.contains(value)) {
+                               return;
+                       }
+                       
+                       double v = domainAxis.valueToJava2D(value, dataArea, plot.getDomainAxisEdge());
+                       
+                       PlotOrientation orientation = plot.getOrientation();
+                       Line2D line = null;
+                       if (orientation == PlotOrientation.HORIZONTAL) {
+                               line = new Line2D.Double(dataArea.getMinX(), v, dataArea.getMaxX(), v);
+                       } else {
+                               line = new Line2D.Double(v, dataArea.getMinY(), v, dataArea.getMaxY());
+                       }
+                       
+                       final Composite originalComposite = g2.getComposite();
+                       g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, marker
+                                       .getAlpha()));
+                       g2.setPaint(marker.getPaint());
+                       g2.setStroke(marker.getStroke());
+                       g2.draw(line);
+                       
+                       String label = marker.getLabel();
+                       RectangleAnchor anchor = marker.getLabelAnchor();
+                       if (label != null) {
+                               Font labelFont = marker.getLabelFont();
+                               g2.setFont(labelFont);
+                               g2.setPaint(marker.getLabelPaint());
+                               Point2D coordinates = calculateDomainMarkerTextAnchorPoint(g2,
+                                               orientation, dataArea, line.getBounds2D(), marker
+                                                               .getLabelOffset(), LengthAdjustmentType.EXPAND, anchor);
+                               
+                               // Changed:
+                               TextAnchor textAnchor = TextAnchor.TOP_RIGHT;
+                               TextUtilities.drawRotatedString(label, g2, (float) coordinates.getX() + 2,
+                                               (float) coordinates.getY(), textAnchor,
+                                               -Math.PI / 2, textAnchor);
+                       }
+                       g2.setComposite(originalComposite);
+               }
+               
+       }
+       
+}
index eb646dc5150d3e43ef092bfb7c27681748a20d40..664d32da21383bb65cd63951b1ef6de9c4d71937 100644 (file)
@@ -33,9 +33,14 @@ import net.sf.openrocket.unit.Unit;
 import net.sf.openrocket.util.GUIUtil;
 import net.sf.openrocket.util.Icons;
 
+/**
+ * Panel that displays the simulation plot options to the user.
+ * 
+ * @author Sampo Niskanen <sampo.niskanen@iki.fi>
+ */
 public class SimulationPlotPanel extends JPanel {
        private static final Translator trans = Application.getTranslator();
-
+       
        // TODO: LOW: Should these be somewhere else?
        public static final int AUTO = -1;
        public static final int LEFT = 0;
@@ -238,7 +243,7 @@ public class SimulationPlotPanel extends JPanel {
                                        JOptionPane.showMessageDialog(SimulationPlotPanel.this,
                                                        //// A maximum of 15 plots is allowed.
                                                        //// Cannot add plot
-                                                       trans.get("simplotpanel.OptionPane.lbl1"), 
+                                                       trans.get("simplotpanel.OptionPane.lbl1"),
                                                        trans.get("simplotpanel.OptionPane.lbl2"),
                                                        JOptionPane.ERROR_MESSAGE);
                                        return;
@@ -287,7 +292,7 @@ public class SimulationPlotPanel extends JPanel {
                        @Override
                        public void actionPerformed(ActionEvent e) {
                                defaultConfiguration = configuration.clone();
-                               PlotDialog.showPlot(SwingUtilities.getWindowAncestor(SimulationPlotPanel.this),
+                               SimulationPlotDialog.showPlot(SwingUtilities.getWindowAncestor(SimulationPlotPanel.this),
                                                simulation, configuration);
                        }
                });
@@ -375,7 +380,7 @@ public class SimulationPlotPanel extends JPanel {
                                public void itemStateChanged(ItemEvent e) {
                                        if (modifying > 0)
                                                return;
-                                       Unit unit = (Unit) unitSelector.getSelectedUnit();
+                                       Unit unit = unitSelector.getSelectedUnit();
                                        configuration.setPlotDataUnit(index, unit);
                                }
                        });
index e131144477cdab6829b7db3cfc927c48216c7fd9..d149b83cacdade4da3d9f6264040c7db830d8b7b 100644 (file)
@@ -456,7 +456,7 @@ public class DesignReport {
                FlightData flight = null;
                try {
                        Simulation simulation = Prefs.getBackgroundSimulation(duplicate);
-                       simulation.getConditions().setMotorConfigurationID(motorId);
+                       simulation.getOptions().setMotorConfigurationID(motorId);
                        simulation.simulate();
                        flight = simulation.getSimulatedData();
                } catch (SimulationException e1) {
index c1b9caee7f60a30ea0964ffced3c2515bc789689..53dc895d9a413805f656aa3048a8503e580bcc84 100644 (file)
@@ -5,20 +5,21 @@ import java.awt.geom.Path2D;
 import java.util.ArrayList;
 
 import net.sf.openrocket.util.Coordinate;
+import net.sf.openrocket.util.MathUtil;
 import net.sf.openrocket.util.Transformation;
 
 
 public class SymmetricComponentShapes extends RocketComponentShapes {
        private static final int MINPOINTS = 91;
-       private static final double ACCEPTABLE_ANGLE = Math.cos(7.0*Math.PI/180.0);
+       private static final double ACCEPTABLE_ANGLE = Math.cos(7.0 * Math.PI / 180.0);
        
        // TODO: HIGH: adaptiveness sucks, remove it.
        
        // TODO: LOW: Uses only first component of cluster (not currently clusterable)
        
-       public static Shape[] getShapesSide(net.sf.openrocket.rocketcomponent.RocketComponent component, 
+       public static Shape[] getShapesSide(net.sf.openrocket.rocketcomponent.RocketComponent component,
                        Transformation transformation) {
-               net.sf.openrocket.rocketcomponent.SymmetricComponent c = (net.sf.openrocket.rocketcomponent.SymmetricComponent)component;
+               net.sf.openrocket.rocketcomponent.SymmetricComponent c = (net.sf.openrocket.rocketcomponent.SymmetricComponent) component;
                int i;
                
                final double delta = 0.0000001;
@@ -26,44 +27,44 @@ public class SymmetricComponentShapes extends RocketComponentShapes {
                
                ArrayList<Coordinate> points = new ArrayList<Coordinate>();
                x = delta;
-               points.add(new Coordinate(x,c.getRadius(x),0));
-               for (i=1; i < MINPOINTS-1; i++) {
-                       x = c.getLength()*i/(MINPOINTS-1);
-                       points.add(new Coordinate(x,c.getRadius(x),0));
+               points.add(new Coordinate(x, c.getRadius(x), 0));
+               for (i = 1; i < MINPOINTS - 1; i++) {
+                       x = c.getLength() * i / (MINPOINTS - 1);
+                       points.add(new Coordinate(x, c.getRadius(x), 0));
                        //System.out.println("Starting with x="+x);
                }
                x = c.getLength() - delta;
-               points.add(new Coordinate(x,c.getRadius(x),0));
+               points.add(new Coordinate(x, c.getRadius(x), 0));
                
-               
-               i=0;
-               while (i < points.size()-2) {
-                       if (angleAcceptable(points.get(i),points.get(i+1),points.get(i+2)) ||
-                                       points.get(i+1).x - points.get(i).x < 0.001) { // 1mm
+
+               i = 0;
+               while (i < points.size() - 2) {
+                       if (angleAcceptable(points.get(i), points.get(i + 1), points.get(i + 2)) ||
+                                       points.get(i + 1).x - points.get(i).x < 0.001) { // 1mm
                                i++;
                                continue;
                        }
-
+                       
                        // Split the longer of the areas
                        int n;
-                       if (points.get(i+2).x-points.get(i+1).x > points.get(i+1).x-points.get(i).x)
-                               n = i+1;
+                       if (points.get(i + 2).x - points.get(i + 1).x > points.get(i + 1).x - points.get(i).x)
+                               n = i + 1;
                        else
                                n = i;
                        
-                       x = (points.get(n).x + points.get(n+1).x)/2;
-                       points.add(n+1,new Coordinate(x,c.getRadius(x),0));
+                       x = (points.get(n).x + points.get(n + 1).x) / 2;
+                       points.add(n + 1, new Coordinate(x, c.getRadius(x), 0));
                }
                
 
                //System.out.println("Final points: "+points.size());
                
                final int len = points.size();
-
-               for (i=0; i < len; i++) {
+               
+               for (i = 0; i < len; i++) {
                        points.set(i, c.toAbsolute(points.get(i))[0]);
                }
-
+               
                /*   Show points:
                Shape[] s = new Shape[len+1];
                final double d=0.001;
@@ -71,29 +72,30 @@ public class SymmetricComponentShapes extends RocketComponentShapes {
                        s[i] = new Ellipse2D.Double(points.get(i).x()-d/2,points.get(i).y()-d/2,d,d);
                }
                */
-               
+
                //System.out.println("here");
                
                // TODO: LOW: curved path instead of linear
                Path2D.Double path = new Path2D.Double();
-               path.moveTo(points.get(len-1).x*S, points.get(len-1).y*S);
-               for (i=len-2; i>=0; i--) {
-                       path.lineTo(points.get(i).x*S, points.get(i).y*S);
+               path.moveTo(points.get(len - 1).x * S, points.get(len - 1).y * S);
+               for (i = len - 2; i >= 0; i--) {
+                       path.lineTo(points.get(i).x * S, points.get(i).y * S);
                }
-               for (i=0; i<len; i++) {
-                       path.lineTo(points.get(i).x*S, -points.get(i).y*S);
+               for (i = 0; i < len; i++) {
+                       path.lineTo(points.get(i).x * S, -points.get(i).y * S);
                }
-               path.lineTo(points.get(len-1).x*S, points.get(len-1).y*S);
+               path.lineTo(points.get(len - 1).x * S, points.get(len - 1).y * S);
                path.closePath();
                
                //s[len] = path;
                //return s;
-               return new Shape[]{ path };
+               return new Shape[] { path };
        }
-
+       
        private static boolean angleAcceptable(Coordinate v1, Coordinate v2, Coordinate v3) {
-               return (cosAngle(v1,v2,v3) > ACCEPTABLE_ANGLE);
+               return (cosAngle(v1, v2, v3) > ACCEPTABLE_ANGLE);
        }
+       
        /*
         * cosAngle = v1.v2 / |v1|*|v2| = v1.v2 / sqrt(v1.v1*v2.v2)
         */
@@ -101,7 +103,7 @@ public class SymmetricComponentShapes extends RocketComponentShapes {
                double cos;
                double len;
                cos = Coordinate.dot(v1.sub(v2), v2.sub(v3));
-               len = Math.sqrt(v1.sub(v2).length2() * v2.sub(v3).length2());
-               return cos/len;
+               len = MathUtil.safeSqrt(v1.sub(v2).length2() * v2.sub(v3).length2());
+               return cos / len;
        }
 }
index 5a515ba765e15d1155dad79ed7632721e1ddbde4..63d2f469838b03b5fb6c06c13ff385fb7b4e075e 100644 (file)
@@ -13,41 +13,46 @@ import net.sf.openrocket.util.Prefs;
 
 
 public abstract class AbstractScaleFigure extends JPanel implements ScaleFigure {
-
-       // Number of pixels to leave at edges when fitting figure
-       public static final int BORDER_PIXELS_WIDTH=30;
-       public static final int BORDER_PIXELS_HEIGHT=20;
        
+       // Number of pixels to leave at edges when fitting figure
+       private static final int DEFAULT_BORDER_PIXELS_WIDTH = 30;
+       private static final int DEFAULT_BORDER_PIXELS_HEIGHT = 20;
        
-       protected final double dpi;
 
+       protected final double dpi;
+       
        protected double scale = 1.0;
        protected double scaling = 1.0;
        
+       protected int borderPixelsWidth = DEFAULT_BORDER_PIXELS_WIDTH;
+       protected int borderPixelsHeight = DEFAULT_BORDER_PIXELS_HEIGHT;
+       
        protected final List<ChangeListener> listeners = new LinkedList<ChangeListener>();
        
-
+       
        public AbstractScaleFigure() {
                this.dpi = Prefs.getDPI();
                this.scaling = 1.0;
-               this.scale = dpi/0.0254*scaling;
+               this.scale = dpi / 0.0254 * scaling;
                
                setBackground(Color.WHITE);
                setOpaque(true);
        }
        
        
-       
+
        public abstract void updateFigure();
+       
        public abstract double getFigureWidth();
+       
        public abstract double getFigureHeight();
        
-
+       
        @Override
        public double getScaling() {
                return scaling;
        }
-
+       
        @Override
        public double getAbsoluteScale() {
                return scale;
@@ -64,49 +69,61 @@ public abstract class AbstractScaleFigure extends JPanel implements ScaleFigure
                if (Math.abs(this.scaling - scaling) < 0.01)
                        return;
                this.scaling = scaling;
-               this.scale = dpi/0.0254*scaling;
+               this.scale = dpi / 0.0254 * scaling;
                updateFigure();
        }
        
        @Override
        public void setScaling(Dimension bounds) {
                double zh = 1, zv = 1;
-               int w = bounds.width - 2*BORDER_PIXELS_WIDTH -20;
-               int h = bounds.height - 2*BORDER_PIXELS_HEIGHT -20;
+               int w = bounds.width - 2 * borderPixelsWidth - 20;
+               int h = bounds.height - 2 * borderPixelsHeight - 20;
                
                if (w < 10)
                        w = 10;
                if (h < 10)
                        h = 10;
                
-               zh = ((double)w) / getFigureWidth();
-               zv = ((double)h) / getFigureHeight();
-                       
-               double s = Math.min(zh, zv)/dpi*0.0254 - 0.001;
+               zh = (w) / getFigureWidth();
+               zv = (h) / getFigureHeight();
+               
+               double s = Math.min(zh, zv) / dpi * 0.0254 - 0.001;
                
                setScaling(s);
        }
-
        
-
+       
+       @Override
+       public Dimension getBorderPixels() {
+               return new Dimension(borderPixelsWidth, borderPixelsHeight);
+       }
+       
+       @Override
+       public void setBorderPixels(int width, int height) {
+               this.borderPixelsWidth = width;
+               this.borderPixelsHeight = height;
+       }
+       
+       
        @Override
        public void addChangeListener(ChangeListener listener) {
-               listeners.add(0,listener);
+               listeners.add(0, listener);
        }
-
+       
        @Override
        public void removeChangeListener(ChangeListener listener) {
                listeners.remove(listener);
        }
        
        private ChangeEvent changeEvent = null;
+       
        protected void fireChangeEvent() {
                ChangeListener[] list = listeners.toArray(new ChangeListener[0]);
-               for (ChangeListener l: list) {
+               for (ChangeListener l : list) {
                        if (changeEvent == null)
                                changeEvent = new ChangeEvent(this);
                        l.stateChanged(changeEvent);
                }
        }
-
+       
 }
index 29e2112c4003fff47ad342d276a9c2c3259f762a..0f08be213a67770443bf123493bbfb368af84114 100644 (file)
@@ -25,7 +25,7 @@ import net.sf.openrocket.util.MathUtil;
 // TODO: MEDIUM:  the figure jumps and bugs when using automatic fitting
 
 public class FinPointFigure extends AbstractScaleFigure {
-
+       
        private static final int BOX_SIZE = 4;
        
        private final FreeformFinSet finset;
@@ -45,48 +45,48 @@ public class FinPointFigure extends AbstractScaleFigure {
                this.finset = finset;
        }
        
-
+       
        @Override
        public void paintComponent(Graphics g) {
                super.paintComponent(g);
                Graphics2D g2 = (Graphics2D) g;
-
+               
                if (modID != finset.getRocket().getAerodynamicModID()) {
                        modID = finset.getRocket().getAerodynamicModID();
                        calculateDimensions();
                }
-
                
+
                double tx, ty;
                // Calculate translation for figure centering
-               if (figureWidth*scale + 2*BORDER_PIXELS_WIDTH < getWidth()) {
-
+               if (figureWidth * scale + 2 * borderPixelsWidth < getWidth()) {
+                       
                        // Figure fits in the viewport
-                       tx = (getWidth()-figureWidth*scale)/2 - minX*scale;
-
+                       tx = (getWidth() - figureWidth * scale) / 2 - minX * scale;
+                       
                } else {
-
+                       
                        // Figure does not fit in viewport
-                       tx = BORDER_PIXELS_WIDTH - minX*scale;
+                       tx = borderPixelsWidth - minX * scale;
                        
                }
                
 
-               if (figureHeight*scale + 2*BORDER_PIXELS_HEIGHT < getHeight()) {
-                       ty = getHeight() - BORDER_PIXELS_HEIGHT;
+               if (figureHeight * scale + 2 * borderPixelsHeight < getHeight()) {
+                       ty = getHeight() - borderPixelsHeight;
                } else {
-                       ty = BORDER_PIXELS_HEIGHT + figureHeight*scale;
+                       ty = borderPixelsHeight + figureHeight * scale;
                }
                
-               if (Math.abs(translateX - tx)>1 || Math.abs(translateY - ty)>1) {
+               if (Math.abs(translateX - tx) > 1 || Math.abs(translateY - ty) > 1) {
                        // Origin has changed, fire event
                        translateX = tx;
                        translateY = ty;
                        fireChangeEvent();
                }
                
-               
-               if (Math.abs(translateX - tx)>1 || Math.abs(translateY - ty)>1) {
+
+               if (Math.abs(translateX - tx) > 1 || Math.abs(translateY - ty) > 1) {
                        // Origin has changed, fire event
                        translateX = tx;
                        translateY = ty;
@@ -97,121 +97,121 @@ public class FinPointFigure extends AbstractScaleFigure {
                // Calculate and store the transformation used
                transform = new AffineTransform();
                transform.translate(translateX, translateY);
-               transform.scale(scale/EXTRA_SCALE, -scale/EXTRA_SCALE);
+               transform.scale(scale / EXTRA_SCALE, -scale / EXTRA_SCALE);
                
                // TODO: HIGH:  border Y-scale upwards
-
+               
                g2.transform(transform);
                
                // Set rendering hints appropriately
-               g2.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, 
+               g2.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL,
                                RenderingHints.VALUE_STROKE_NORMALIZE);
-               g2.setRenderingHint(RenderingHints.KEY_RENDERING, 
+               g2.setRenderingHint(RenderingHints.KEY_RENDERING,
                                RenderingHints.VALUE_RENDER_QUALITY);
-               g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, 
+               g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                                RenderingHints.VALUE_ANTIALIAS_ON);
+               
 
 
-               
                Rectangle visible = g2.getClipBounds();
-               double x0 = ((double)visible.x-3)/EXTRA_SCALE;
-               double x1 = ((double)visible.x+visible.width+4)/EXTRA_SCALE;
-               double y0 = ((double)visible.y-3)/EXTRA_SCALE;
-               double y1 = ((double)visible.y+visible.height+4)/EXTRA_SCALE;
-               
+               double x0 = ((double) visible.x - 3) / EXTRA_SCALE;
+               double x1 = ((double) visible.x + visible.width + 4) / EXTRA_SCALE;
+               double y0 = ((double) visible.y - 3) / EXTRA_SCALE;
+               double y1 = ((double) visible.y + visible.height + 4) / EXTRA_SCALE;
                
+
                // Background grid
                
-               g2.setStroke(new BasicStroke((float)(1.0*EXTRA_SCALE/scale),
+               g2.setStroke(new BasicStroke((float) (1.0 * EXTRA_SCALE / scale),
                                BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL));
-               g2.setColor(new Color(0,0,255,30));
-
+               g2.setColor(new Color(0, 0, 255, 30));
+               
                Unit unit;
                if (this.getParent() != null &&
                                this.getParent().getParent() instanceof ScaleScrollPane) {
-                       unit = ((ScaleScrollPane)this.getParent().getParent()).getCurrentUnit();
+                       unit = ((ScaleScrollPane) this.getParent().getParent()).getCurrentUnit();
                } else {
                        unit = UnitGroup.UNITS_LENGTH.getDefaultUnit();
                }
                
                // vertical
-        Tick[] ticks = unit.getTicks(x0, x1, 
-                       ScaleScrollPane.MINOR_TICKS/scale, 
-                       ScaleScrollPane.MAJOR_TICKS/scale);
-        Line2D.Double line = new Line2D.Double();
-        for (Tick t: ticks) {
-               if (t.major) {
-                       line.setLine(t.value*EXTRA_SCALE, y0*EXTRA_SCALE, 
-                                       t.value*EXTRA_SCALE, y1*EXTRA_SCALE);
-                       g2.draw(line);
-               }
-        }
-        
-        // horizontal
-        ticks = unit.getTicks(y0, y1, 
-                       ScaleScrollPane.MINOR_TICKS/scale, 
-                       ScaleScrollPane.MAJOR_TICKS/scale);
-        for (Tick t: ticks) {
-               if (t.major) {
-                       line.setLine(x0*EXTRA_SCALE, t.value*EXTRA_SCALE, 
-                                       x1*EXTRA_SCALE, t.value*EXTRA_SCALE);
-                       g2.draw(line);
-               }
-        }
-               
-        
-        
-        
+               Tick[] ticks = unit.getTicks(x0, x1,
+                               ScaleScrollPane.MINOR_TICKS / scale,
+                               ScaleScrollPane.MAJOR_TICKS / scale);
+               Line2D.Double line = new Line2D.Double();
+               for (Tick t : ticks) {
+                       if (t.major) {
+                               line.setLine(t.value * EXTRA_SCALE, y0 * EXTRA_SCALE,
+                                               t.value * EXTRA_SCALE, y1 * EXTRA_SCALE);
+                               g2.draw(line);
+                       }
+               }
+               
+               // horizontal
+               ticks = unit.getTicks(y0, y1,
+                               ScaleScrollPane.MINOR_TICKS / scale,
+                               ScaleScrollPane.MAJOR_TICKS / scale);
+               for (Tick t : ticks) {
+                       if (t.major) {
+                               line.setLine(x0 * EXTRA_SCALE, t.value * EXTRA_SCALE,
+                                               x1 * EXTRA_SCALE, t.value * EXTRA_SCALE);
+                               g2.draw(line);
+                       }
+               }
+               
+
+
+
 
                // Base rocket line
-               g2.setStroke(new BasicStroke((float)(3.0*EXTRA_SCALE/scale),
-                               BasicStroke.CAP_BUTT,BasicStroke.JOIN_BEVEL));
+               g2.setStroke(new BasicStroke((float) (3.0 * EXTRA_SCALE / scale),
+                               BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL));
                g2.setColor(Color.GRAY);
                
-               g2.drawLine((int)(x0*EXTRA_SCALE), 0, (int)(x1*EXTRA_SCALE), 0);
-               
+               g2.drawLine((int) (x0 * EXTRA_SCALE), 0, (int) (x1 * EXTRA_SCALE), 0);
                
+
                // Fin shape
                Coordinate[] points = finset.getFinPoints();
                Path2D.Double shape = new Path2D.Double();
                shape.moveTo(0, 0);
-               for (int i=1; i < points.length; i++) {
-                       shape.lineTo(points[i].x*EXTRA_SCALE, points[i].y*EXTRA_SCALE);
+               for (int i = 1; i < points.length; i++) {
+                       shape.lineTo(points[i].x * EXTRA_SCALE, points[i].y * EXTRA_SCALE);
                }
                
-               g2.setStroke(new BasicStroke((float)(1.0*EXTRA_SCALE/scale),
-                               BasicStroke.CAP_BUTT,BasicStroke.JOIN_BEVEL));
+               g2.setStroke(new BasicStroke((float) (1.0 * EXTRA_SCALE / scale),
+                               BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL));
                g2.setColor(Color.BLACK);
                g2.draw(shape);
-
                
+
                // Fin point boxes
-               g2.setColor(new Color(150,0,0));
-               double s = BOX_SIZE*EXTRA_SCALE/scale;
+               g2.setColor(new Color(150, 0, 0));
+               double s = BOX_SIZE * EXTRA_SCALE / scale;
                handles = new Rectangle2D.Double[points.length];
-               for (int i=0; i < points.length; i++) {
+               for (int i = 0; i < points.length; i++) {
                        Coordinate c = points[i];
-                       handles[i] = new Rectangle2D.Double(c.x*EXTRA_SCALE-s, c.y*EXTRA_SCALE-s, 2*s, 2*s);
+                       handles[i] = new Rectangle2D.Double(c.x * EXTRA_SCALE - s, c.y * EXTRA_SCALE - s, 2 * s, 2 * s);
                        g2.draw(handles[i]);
                }
                
        }
-
        
        
+
        public int getIndexByPoint(double x, double y) {
                if (handles == null)
                        return -1;
                
                // Calculate point in shapes' coordinates
-               Point2D.Double p = new Point2D.Double(x,y);
+               Point2D.Double p = new Point2D.Double(x, y);
                try {
-                       transform.inverseTransform(p,p);
+                       transform.inverseTransform(p, p);
                } catch (NoninvertibleTransformException e) {
                        return -1;
                }
-
-               for (int i=0; i < handles.length; i++) {
+               
+               for (int i = 0; i < handles.length; i++) {
                        if (handles[i].contains(p))
                                return i;
                }
@@ -224,9 +224,9 @@ public class FinPointFigure extends AbstractScaleFigure {
                        return -1;
                
                // Calculate point in shapes' coordinates
-               Point2D.Double p = new Point2D.Double(x,y);
+               Point2D.Double p = new Point2D.Double(x, y);
                try {
-                       transform.inverseTransform(p,p);
+                       transform.inverseTransform(p, p);
                } catch (NoninvertibleTransformException e) {
                        return -1;
                }
@@ -234,22 +234,22 @@ public class FinPointFigure extends AbstractScaleFigure {
                double x0 = p.x / EXTRA_SCALE;
                double y0 = p.y / EXTRA_SCALE;
                double delta = BOX_SIZE / scale;
-
-               System.out.println("Point: "+x0+","+y0);
-               System.out.println("delta: "+(BOX_SIZE/scale));
+               
+               System.out.println("Point: " + x0 + "," + y0);
+               System.out.println("delta: " + (BOX_SIZE / scale));
                
                Coordinate[] points = finset.getFinPoints();
-               for (int i=1; i < points.length; i++) {
-                       double x1 = points[i-1].x;
-                       double y1 = points[i-1].y;
+               for (int i = 1; i < points.length; i++) {
+                       double x1 = points[i - 1].x;
+                       double y1 = points[i - 1].y;
                        double x2 = points[i].x;
                        double y2 = points[i].y;
                        
-//                     System.out.println("point1:"+x1+","+y1+" point2:"+x2+","+y2);
+                       //                      System.out.println("point1:"+x1+","+y1+" point2:"+x2+","+y2);
                        
-                       double u = Math.abs((x2-x1)*(y1-y0) - (x1-x0)*(y2-y1)) / 
-                                               MathUtil.hypot(x2-x1, y2-y1);
-                       System.out.println("Distance of segment "+i+" is "+u);
+                       double u = Math.abs((x2 - x1) * (y1 - y0) - (x1 - x0) * (y2 - y1)) /
+                                               MathUtil.hypot(x2 - x1, y2 - y1);
+                       System.out.println("Distance of segment " + i + " is " + u);
                        if (u < delta)
                                return i;
                }
@@ -259,14 +259,14 @@ public class FinPointFigure extends AbstractScaleFigure {
        
        
        public Point2D.Double convertPoint(double x, double y) {
-               Point2D.Double p = new Point2D.Double(x,y);
+               Point2D.Double p = new Point2D.Double(x, y);
                try {
-                       transform.inverseTransform(p,p);
+                       transform.inverseTransform(p, p);
                } catch (NoninvertibleTransformException e) {
-                       assert(false): "Should not occur";
-                       return new Point2D.Double(0,0);
+                       assert (false) : "Should not occur";
+                       return new Point2D.Double(0, 0);
                }
-       
+               
                p.setLocation(p.x / EXTRA_SCALE, p.y / EXTRA_SCALE);
                return p;
        }
@@ -279,9 +279,9 @@ public class FinPointFigure extends AbstractScaleFigure {
                        modID = finset.getRocket().getAerodynamicModID();
                        calculateDimensions();
                }
-               return new Dimension((int)translateX, (int)translateY);
+               return new Dimension((int) translateX, (int) translateY);
        }
-
+       
        @Override
        public double getFigureWidth() {
                if (modID != finset.getRocket().getAerodynamicModID()) {
@@ -290,7 +290,7 @@ public class FinPointFigure extends AbstractScaleFigure {
                }
                return figureWidth;
        }
-
+       
        @Override
        public double getFigureHeight() {
                if (modID != finset.getRocket().getAerodynamicModID()) {
@@ -299,14 +299,14 @@ public class FinPointFigure extends AbstractScaleFigure {
                }
                return figureHeight;
        }
-
+       
        
        private void calculateDimensions() {
                minX = 0;
                maxX = 0;
                maxY = 0;
                
-               for (Coordinate c: finset.getFinPoints()) {
+               for (Coordinate c : finset.getFinPoints()) {
                        if (c.x < minX)
                                minX = c.x;
                        if (c.x > maxX)
@@ -322,8 +322,8 @@ public class FinPointFigure extends AbstractScaleFigure {
                figureHeight = maxY;
                
 
-               Dimension d = new Dimension((int)(figureWidth*scale+2*BORDER_PIXELS_WIDTH),
-                               (int)(figureHeight*scale+2*BORDER_PIXELS_HEIGHT));
+               Dimension d = new Dimension((int) (figureWidth * scale + 2 * borderPixelsWidth),
+                               (int) (figureHeight * scale + 2 * borderPixelsHeight));
                
                if (!d.equals(getPreferredSize()) || !d.equals(getMinimumSize())) {
                        setPreferredSize(d);
@@ -339,6 +339,6 @@ public class FinPointFigure extends AbstractScaleFigure {
                repaint();
        }
        
-       
+
 
 }
index ea1f98c7180c4bf3d705f1c86d8c70b737a218a2..2f7556e2ab52e35f86c7b0b91f785cc575ac6c72 100644 (file)
@@ -1,20 +1,6 @@
 package net.sf.openrocket.gui.scalefigure;
 
 
-import net.sf.openrocket.gui.figureelements.FigureElement;
-import net.sf.openrocket.gui.main.ExceptionHandler;
-import net.sf.openrocket.motor.Motor;
-import net.sf.openrocket.rocketcomponent.Configuration;
-import net.sf.openrocket.rocketcomponent.MotorMount;
-import net.sf.openrocket.rocketcomponent.RocketComponent;
-import net.sf.openrocket.util.BugException;
-import net.sf.openrocket.util.Coordinate;
-import net.sf.openrocket.util.LineStyle;
-import net.sf.openrocket.util.MathUtil;
-import net.sf.openrocket.util.Prefs;
-import net.sf.openrocket.util.Reflection;
-import net.sf.openrocket.util.Transformation;
-
 import java.awt.BasicStroke;
 import java.awt.Color;
 import java.awt.Dimension;
@@ -33,6 +19,20 @@ import java.util.Collection;
 import java.util.Iterator;
 import java.util.LinkedHashSet;
 
+import net.sf.openrocket.gui.figureelements.FigureElement;
+import net.sf.openrocket.gui.main.ExceptionHandler;
+import net.sf.openrocket.motor.Motor;
+import net.sf.openrocket.rocketcomponent.Configuration;
+import net.sf.openrocket.rocketcomponent.MotorMount;
+import net.sf.openrocket.rocketcomponent.RocketComponent;
+import net.sf.openrocket.util.BugException;
+import net.sf.openrocket.util.Coordinate;
+import net.sf.openrocket.util.LineStyle;
+import net.sf.openrocket.util.MathUtil;
+import net.sf.openrocket.util.Prefs;
+import net.sf.openrocket.util.Reflection;
+import net.sf.openrocket.util.Transformation;
+
 /**
  * A <code>ScaleFigure</code> that draws a complete rocket.  Extra information can
  * be added to the figure by the methods {@link #addRelativeExtra(FigureElement)},
@@ -40,7 +40,6 @@ import java.util.LinkedHashSet;
  * 
  * @author Sampo Niskanen <sampo.niskanen@iki.fi>
  */
-
 public class RocketFigure extends AbstractScaleFigure {
        private static final long serialVersionUID = 1L;
        
@@ -53,31 +52,31 @@ public class RocketFigure extends AbstractScaleFigure {
        // Width for drawing normal and selected components
        public static final double NORMAL_WIDTH = 1.0;
        public static final double SELECTED_WIDTH = 2.0;
-
        
-       private final Configuration configuration;
+
+       private Configuration configuration;
        private RocketComponent[] selection = new RocketComponent[0];
        
        private int type = TYPE_SIDE;
-
+       
        private double rotation;
        private Transformation transformation;
        
        private double translateX, translateY;
        
-       
-       
+
+
        /*
         * figureComponents contains the corresponding RocketComponents of the figureShapes
         */
        private final ArrayList<Shape> figureShapes = new ArrayList<Shape>();
-       private final ArrayList<RocketComponent> figureComponents = 
-               new ArrayList<RocketComponent>();
+       private final ArrayList<RocketComponent> figureComponents =
+                       new ArrayList<RocketComponent>();
        
-       private double minX=0, maxX=0, maxR=0;
+       private double minX = 0, maxX = 0, maxR = 0;
        // Figure width and height in SI-units and pixels
-       private double figureWidth=0, figureHeight=0;
-       private int figureWidthPx=0, figureHeightPx=0;
+       private double figureWidth = 0, figureHeight = 0;
+       private int figureWidthPx = 0, figureHeightPx = 0;
        
        private AffineTransform g2transformation = null;
        
@@ -96,26 +95,36 @@ public class RocketFigure extends AbstractScaleFigure {
                this.rotation = 0.0;
                this.transformation = Transformation.rotate_x(0.0);
                
-               calculateSize();
                updateFigure();
        }
        
        
+       /**
+        * Set the configuration displayed by the figure.  It may use the same or different rocket.
+        * 
+        * @param configuration         the configuration to display.
+        */
+       public void setConfiguration(Configuration configuration) {
+               this.configuration = configuration;
+               updateFigure();
+       }
+       
        
+       @Override
        public Dimension getOrigin() {
-               return new Dimension((int)translateX, (int)translateY);
+               return new Dimension((int) translateX, (int) translateY);
        }
        
        @Override
        public double getFigureHeight() {
                return figureHeight;
        }
-
+       
        @Override
        public double getFigureWidth() {
                return figureWidth;
        }
-
+       
        
        public RocketComponent[] getSelection() {
                return selection;
@@ -154,19 +163,18 @@ public class RocketFigure extends AbstractScaleFigure {
        
        public void setType(int type) {
                if (type != TYPE_BACK && type != TYPE_SIDE) {
-                       throw new IllegalArgumentException("Illegal type: "+type);
+                       throw new IllegalArgumentException("Illegal type: " + type);
                }
                if (this.type == type)
                        return;
                this.type = type;
                updateFigure();
        }
-
-       
        
        
 
 
+
        /**
         * Updates the figure shapes and figure size.
         */
@@ -176,11 +184,11 @@ public class RocketFigure extends AbstractScaleFigure {
                figureComponents.clear();
                
                calculateSize();
-
+               
                // Get shapes for all active components
-               for (RocketComponent c: configuration) {
+               for (RocketComponent c : configuration) {
                        Shape[] s = getShapes(c);
-                       for (int i=0; i < s.length; i++) {
+                       for (int i = 0; i < s.length; i++) {
                                figureShapes.add(s[i]);
                                figureComponents.add(c);
                        }
@@ -189,7 +197,7 @@ public class RocketFigure extends AbstractScaleFigure {
                repaint();
                fireChangeEvent();
        }
-
+       
        
        public void addRelativeExtra(FigureElement p) {
                relativeExtra.add(p);
@@ -216,7 +224,7 @@ public class RocketFigure extends AbstractScaleFigure {
                absoluteExtra.clear();
        }
        
-
+       
        /**
         * Paints the rocket on to the Graphics element.
         * <p>
@@ -227,39 +235,39 @@ public class RocketFigure extends AbstractScaleFigure {
        @Override
        public void paintComponent(Graphics g) {
                super.paintComponent(g);
-               Graphics2D g2 = (Graphics2D)g;
-               
+               Graphics2D g2 = (Graphics2D) g;
                
+
                AffineTransform baseTransform = g2.getTransform();
                
                // Update figure shapes if necessary
                if (figureShapes == null)
                        updateFigure();
-
+               
 
                double tx, ty;
                // Calculate translation for figure centering
-               if (figureWidthPx + 2*BORDER_PIXELS_WIDTH < getWidth()) {
-
+               if (figureWidthPx + 2 * borderPixelsWidth < getWidth()) {
+                       
                        // Figure fits in the viewport
                        if (type == TYPE_BACK)
-                               tx = getWidth()/2;
-                       else 
-                               tx = (getWidth()-figureWidthPx)/2 - minX*scale;
-
+                               tx = getWidth() / 2;
+                       else
+                               tx = (getWidth() - figureWidthPx) / 2 - minX * scale;
+                       
                } else {
-
+                       
                        // Figure does not fit in viewport
                        if (type == TYPE_BACK)
-                               tx = BORDER_PIXELS_WIDTH + figureWidthPx/2;
-                       else 
-                               tx = BORDER_PIXELS_WIDTH - minX*scale;
+                               tx = borderPixelsWidth + figureWidthPx / 2;
+                       else
+                               tx = borderPixelsWidth - minX * scale;
                        
                }
-
-        ty = computeTy(figureHeightPx);
-
-        if (Math.abs(translateX - tx)>1 || Math.abs(translateY - ty)>1) {
+               
+               ty = computeTy(figureHeightPx);
+               
+               if (Math.abs(translateX - tx) > 1 || Math.abs(translateY - ty) > 1) {
                        // Origin has changed, fire event
                        translateX = tx;
                        translateY = ty;
@@ -272,28 +280,28 @@ public class RocketFigure extends AbstractScaleFigure {
                g2transformation = new AffineTransform();
                g2transformation.translate(translateX, translateY);
                // Mirror position Y-axis upwards
-               g2transformation.scale(scale/EXTRA_SCALE, -scale/EXTRA_SCALE);
-
+               g2transformation.scale(scale / EXTRA_SCALE, -scale / EXTRA_SCALE);
+               
                g2.transform(g2transformation);
-
+               
                // Set rendering hints appropriately
-               g2.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, 
+               g2.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL,
                                RenderingHints.VALUE_STROKE_NORMALIZE);
-               g2.setRenderingHint(RenderingHints.KEY_RENDERING, 
+               g2.setRenderingHint(RenderingHints.KEY_RENDERING,
                                RenderingHints.VALUE_RENDER_QUALITY);
-               g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, 
+               g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                                RenderingHints.VALUE_ANTIALIAS_ON);
-
+               
 
                // Draw all shapes
                
-               for (int i=0; i < figureShapes.size(); i++) {
+               for (int i = 0; i < figureShapes.size(); i++) {
                        RocketComponent c = figureComponents.get(i);
                        Shape s = figureShapes.get(i);
                        boolean selected = false;
                        
                        // Check if component is in the selection
-                       for (int j=0; j < selection.length; j++) {
+                       for (int j = 0; j < selection.length; j++) {
                                if (c == selection[j]) {
                                        selected = true;
                                        break;
@@ -306,37 +314,37 @@ public class RocketFigure extends AbstractScaleFigure {
                                color = Prefs.getDefaultColor(c.getClass());
                        }
                        g2.setColor(color);
-
+                       
                        LineStyle style = c.getLineStyle();
                        if (style == null)
                                style = Prefs.getDefaultLineStyle(c.getClass());
                        
                        float[] dashes = style.getDashes();
-                       for (int j=0; j<dashes.length; j++) {
+                       for (int j = 0; j < dashes.length; j++) {
                                dashes[j] *= EXTRA_SCALE / scale;
                        }
                        
                        if (selected) {
-                               g2.setStroke(new BasicStroke((float)(SELECTED_WIDTH*EXTRA_SCALE/scale),
-                                               BasicStroke.CAP_BUTT,BasicStroke.JOIN_BEVEL, 0, dashes, 0));
+                               g2.setStroke(new BasicStroke((float) (SELECTED_WIDTH * EXTRA_SCALE / scale),
+                                               BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL, 0, dashes, 0));
                                g2.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL,
                                                RenderingHints.VALUE_STROKE_PURE);
                        } else {
-                               g2.setStroke(new BasicStroke((float)(NORMAL_WIDTH*EXTRA_SCALE/scale),
-                                               BasicStroke.CAP_BUTT,BasicStroke.JOIN_BEVEL, 0, dashes, 0));
-                               g2.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, 
+                               g2.setStroke(new BasicStroke((float) (NORMAL_WIDTH * EXTRA_SCALE / scale),
+                                               BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL, 0, dashes, 0));
+                               g2.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL,
                                                RenderingHints.VALUE_STROKE_NORMALIZE);
                        }
                        g2.draw(s);
                        
                }
                
-               g2.setStroke(new BasicStroke((float)(NORMAL_WIDTH*EXTRA_SCALE/scale),
-                               BasicStroke.CAP_BUTT,BasicStroke.JOIN_BEVEL));
-               g2.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, 
+               g2.setStroke(new BasicStroke((float) (NORMAL_WIDTH * EXTRA_SCALE / scale),
+                               BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL));
+               g2.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL,
                                RenderingHints.VALUE_STROKE_NORMALIZE);
-
                
+
                // Draw motors
                String motorID = configuration.getMotorConfigurationID();
                Color fillColor = Prefs.getMotorFillColor();
@@ -348,24 +356,24 @@ public class RocketFigure extends AbstractScaleFigure {
                        double length = motor.getLength();
                        double radius = motor.getDiameter() / 2;
                        
-                       Coordinate[] position = ((RocketComponent)mount).toAbsolute(
-                                       new Coordinate(((RocketComponent)mount).getLength() + 
+                       Coordinate[] position = ((RocketComponent) mount).toAbsolute(
+                                       new Coordinate(((RocketComponent) mount).getLength() +
                                                        mount.getMotorOverhang() - length));
                        
-                       for (int i=0; i < position.length; i++) {
+                       for (int i = 0; i < position.length; i++) {
                                position[i] = transformation.transform(position[i]);
                        }
                        
-                       for (Coordinate coord: position) {
+                       for (Coordinate coord : position) {
                                Shape s;
                                if (type == TYPE_SIDE) {
-                                       s = new Rectangle2D.Double(EXTRA_SCALE*coord.x,
-                                                       EXTRA_SCALE*(coord.y - radius), EXTRA_SCALE*length, 
-                                                       EXTRA_SCALE*2*radius);
+                                       s = new Rectangle2D.Double(EXTRA_SCALE * coord.x,
+                                                       EXTRA_SCALE * (coord.y - radius), EXTRA_SCALE * length,
+                                                       EXTRA_SCALE * 2 * radius);
                                } else {
-                                       s = new Ellipse2D.Double(EXTRA_SCALE*(coord.z-radius),
-                                                       EXTRA_SCALE*(coord.y-radius), EXTRA_SCALE*2*radius,
-                                                       EXTRA_SCALE*2*radius);
+                                       s = new Ellipse2D.Double(EXTRA_SCALE * (coord.z - radius),
+                                                       EXTRA_SCALE * (coord.y - radius), EXTRA_SCALE * 2 * radius,
+                                                       EXTRA_SCALE * 2 * radius);
                                }
                                g2.setColor(fillColor);
                                g2.fill(s);
@@ -374,46 +382,46 @@ public class RocketFigure extends AbstractScaleFigure {
                        }
                }
                
-               
-               
+
+
                // Draw relative extras
-               for (FigureElement e: relativeExtra) {
-                       e.paint(g2, scale/EXTRA_SCALE);
+               for (FigureElement e : relativeExtra) {
+                       e.paint(g2, scale / EXTRA_SCALE);
                }
-
+               
                // Draw absolute extras
                g2.setTransform(baseTransform);
                Rectangle rect = this.getVisibleRect();
                
-               for (FigureElement e: absoluteExtra) {
+               for (FigureElement e : absoluteExtra) {
                        e.paint(g2, 1.0, rect);
                }
-
+               
        }
-
-    protected double computeTy (int heightPx) {
-        final double ty;
-        if (heightPx + 2*BORDER_PIXELS_HEIGHT < getHeight()) {
-             ty = getHeight()/2;
-        } else {
-             ty = BORDER_PIXELS_HEIGHT + heightPx/2;
-        }
-        return ty;
-    }
-
-
-    public RocketComponent[] getComponentsByPoint(double x, double y) {
+       
+       protected double computeTy(int heightPx) {
+               final double ty;
+               if (heightPx + 2 * borderPixelsHeight < getHeight()) {
+                       ty = getHeight() / 2;
+               } else {
+                       ty = borderPixelsHeight + heightPx / 2;
+               }
+               return ty;
+       }
+       
+       
+       public RocketComponent[] getComponentsByPoint(double x, double y) {
                // Calculate point in shapes' coordinates
-               Point2D.Double p = new Point2D.Double(x,y);
+               Point2D.Double p = new Point2D.Double(x, y);
                try {
-                       g2transformation.inverseTransform(p,p);
+                       g2transformation.inverseTransform(p, p);
                } catch (NoninvertibleTransformException e) {
                        return new RocketComponent[0];
                }
                
                LinkedHashSet<RocketComponent> l = new LinkedHashSet<RocketComponent>();
-
-               for (int i=0; i<figureShapes.size(); i++) {
+               
+               for (int i = 0; i < figureShapes.size(); i++) {
                        if (figureShapes.get(i).contains(p))
                                l.add(figureComponents.get(i));
                }
@@ -421,7 +429,7 @@ public class RocketFigure extends AbstractScaleFigure {
        }
        
        
-       
+
        /**
         * Gets the shapes required to draw the component.
         * 
@@ -431,34 +439,34 @@ public class RocketFigure extends AbstractScaleFigure {
         */
        private Shape[] getShapes(RocketComponent component) {
                Reflection.Method m;
-
+               
                // Find the appropriate method
                switch (type) {
                case TYPE_SIDE:
-                       m = Reflection.findMethod(ROCKET_FIGURE_PACKAGE, component, ROCKET_FIGURE_SUFFIX, "getShapesSide", 
+                       m = Reflection.findMethod(ROCKET_FIGURE_PACKAGE, component, ROCKET_FIGURE_SUFFIX, "getShapesSide",
                                        RocketComponent.class, Transformation.class);
                        break;
-                       
+               
                case TYPE_BACK:
-                       m = Reflection.findMethod(ROCKET_FIGURE_PACKAGE, component, ROCKET_FIGURE_SUFFIX, "getShapesBack", 
+                       m = Reflection.findMethod(ROCKET_FIGURE_PACKAGE, component, ROCKET_FIGURE_SUFFIX, "getShapesBack",
                                        RocketComponent.class, Transformation.class);
                        break;
-                       
+               
                default:
-                       throw new BugException("Unknown figure type = "+type);
+                       throw new BugException("Unknown figure type = " + type);
                }
                
                if (m == null) {
-                       ExceptionHandler.handleErrorCondition("ERROR: Rocket figure paint method not found for " 
+                       ExceptionHandler.handleErrorCondition("ERROR: Rocket figure paint method not found for "
                                        + component);
                        return new Shape[0];
                }
-
-               return (Shape[])m.invokeStatic(component,transformation);
+               
+               return (Shape[]) m.invokeStatic(component, transformation);
        }
        
        
-       
+
        /**
         * Gets the bounds of the figure, i.e. the maximum extents in the selected dimensions.
         * The bounds are stored in the variables minX, maxX and maxR.
@@ -472,11 +480,11 @@ public class RocketFigure extends AbstractScaleFigure {
                        maxR = 0;
                        return;
                }
-
+               
                minX = Double.MAX_VALUE;
                maxX = Double.MIN_VALUE;
                maxR = 0;
-               for (Coordinate c: bounds) {
+               for (Coordinate c : bounds) {
                        double x = c.x, r = MathUtil.hypot(c.y, c.z);
                        if (x < minX)
                                minX = x;
@@ -489,15 +497,16 @@ public class RocketFigure extends AbstractScaleFigure {
        
        
        public double getBestZoom(Rectangle2D bounds) {
-               double zh=1, zv=1;
+               double zh = 1, zv = 1;
                if (bounds.getWidth() > 0.0001)
-                       zh = (getWidth()-2*BORDER_PIXELS_WIDTH)/bounds.getWidth();
+                       zh = (getWidth() - 2 * borderPixelsWidth) / bounds.getWidth();
                if (bounds.getHeight() > 0.0001)
-                       zv = (getHeight()-2*BORDER_PIXELS_HEIGHT)/bounds.getHeight();
+                       zv = (getHeight() - 2 * borderPixelsHeight) / bounds.getHeight();
                return Math.min(zh, zv);
        }
-
        
+       
+
        /**
         * Calculates the necessary size of the figure and set the PreferredSize 
         * property accordingly.
@@ -507,26 +516,26 @@ public class RocketFigure extends AbstractScaleFigure {
                
                switch (type) {
                case TYPE_SIDE:
-                       figureWidth = maxX-minX;
-                       figureHeight = 2*maxR;
+                       figureWidth = maxX - minX;
+                       figureHeight = 2 * maxR;
                        break;
-                       
+               
                case TYPE_BACK:
-                       figureWidth = 2*maxR;
-                       figureHeight = 2*maxR;
+                       figureWidth = 2 * maxR;
+                       figureHeight = 2 * maxR;
                        break;
-                       
+               
                default:
-                       assert(false): "Should not occur, type="+type;
+                       assert (false) : "Should not occur, type=" + type;
                        figureWidth = 0;
                        figureHeight = 0;
                }
                
-               figureWidthPx = (int)(figureWidth * scale);
-               figureHeightPx = (int)(figureHeight * scale);
-
-               Dimension d = new Dimension(figureWidthPx+2*BORDER_PIXELS_WIDTH,
-                               figureHeightPx+2*BORDER_PIXELS_HEIGHT);
+               figureWidthPx = (int) (figureWidth * scale);
+               figureHeightPx = (int) (figureHeight * scale);
+               
+               Dimension d = new Dimension(figureWidthPx + 2 * borderPixelsWidth,
+                               figureHeightPx + 2 * borderPixelsHeight);
                
                if (!d.equals(getPreferredSize()) || !d.equals(getMinimumSize())) {
                        setPreferredSize(d);
@@ -538,14 +547,14 @@ public class RocketFigure extends AbstractScaleFigure {
        public Rectangle2D getDimensions() {
                switch (type) {
                case TYPE_SIDE:
-                       return new Rectangle2D.Double(minX,-maxR,maxX-minX,2*maxR);
+                       return new Rectangle2D.Double(minX, -maxR, maxX - minX, 2 * maxR);
                        
                case TYPE_BACK:
-                       return new Rectangle2D.Double(-maxR,-maxR,2*maxR,2*maxR);
+                       return new Rectangle2D.Double(-maxR, -maxR, 2 * maxR, 2 * maxR);
                        
                default:
-                       throw new BugException("Illegal figure type = "+type);
+                       throw new BugException("Illegal figure type = " + type);
                }
        }
-
+       
 }
index c367d7e29d3a184c0c56a6daea9b44401048a0a8..920d4617021b85a143ab1be8fb2c3ccca72d8193 100644 (file)
@@ -592,7 +592,7 @@ public class RocketPanel extends JPanel implements TreeSelectionListener, Change
                
                Rocket duplicate = (Rocket) configuration.getRocket().copy();
                Simulation simulation = Prefs.getBackgroundSimulation(duplicate);
-               simulation.getConditions().setMotorConfigurationID(
+               simulation.getOptions().setMotorConfigurationID(
                                configuration.getMotorConfigurationID());
                
                backgroundSimulationWorker = new BackgroundSimulationWorker(simulation);
index 161c8353bd973f4da9832dffd07b44df58a693bf..48440fe336d23d671c1d937ddfe36e0c27fa4bb1 100644 (file)
@@ -48,7 +48,7 @@ public interface ScaleFigure extends ChangeSource {
         */
        public double getScaling();
        
-
+       
        /**
         * Return the scale of the figure on px/m.
         * 
@@ -56,7 +56,7 @@ public interface ScaleFigure extends ChangeSource {
         */
        public double getAbsoluteScale();
        
-
+       
        /**
         * Return the pixel coordinates of the figure origin.
         * 
@@ -64,4 +64,19 @@ public interface ScaleFigure extends ChangeSource {
         */
        public Dimension getOrigin();
        
+       
+       /**
+        * Get the amount of blank space left around the figure.
+        * 
+        * @return      the amount of horizontal and vertical space left on both sides of the figure.
+        */
+       public Dimension getBorderPixels();
+       
+       /**
+        * Set the amount of blank space left around the figure.
+        * 
+        * @param width         the amount of horizontal space left on both sides of the figure.
+        * @param height        the amount of vertical space left on both sides of the figure.
+        */
+       public void setBorderPixels(int width, int height);
 }
index aa46b68240dd688d3d1ac060d662d1162ff3e123..75ec602f10f8f8d16d06c89968f6862bdaa9cf3b 100644 (file)
@@ -43,18 +43,18 @@ import net.sf.openrocket.util.BugException;
  * 
  * @author Sampo Niskanen <sampo.niskanen@iki.fi>
  */
-public class ScaleScrollPane extends JScrollPane 
+public class ScaleScrollPane extends JScrollPane
                implements MouseListener, MouseMotionListener {
        
        public static final int RULER_SIZE = 20;
        public static final int MINOR_TICKS = 3;
        public static final int MAJOR_TICKS = 30;
-
        
+
        private JComponent component;
        private ScaleFigure figure;
        private JViewport viewport;
-
+       
        private DoubleModel rulerUnit;
        private Ruler horizontalRuler;
        private Ruler verticalRuler;
@@ -64,25 +64,34 @@ public class ScaleScrollPane extends JScrollPane
        private boolean fit = false;
        
        
+       /**
+        * Create a scale scroll pane that allows fitting.
+        * 
+        * @param component             the component to contain (must implement ScaleFigure)
+        */
        public ScaleScrollPane(JComponent component) {
                this(component, true);
        }
        
+       /**
+        * Create a scale scroll pane.
+        * 
+        * @param component             the component to contain (must implement ScaleFigure)
+        * @param allowFit              whether automatic fitting of the figure is allowed
+        */
        public ScaleScrollPane(JComponent component, boolean allowFit) {
                super(component);
-//             super(component, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, 
-//                             JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
                
                if (!(component instanceof ScaleFigure)) {
                        throw new IllegalArgumentException("component must implement ScaleFigure");
                }
                
                this.component = component;
-               this.figure = (ScaleFigure)component;
+               this.figure = (ScaleFigure) component;
                this.allowFit = allowFit;
                
 
-               rulerUnit = new DoubleModel(0.0,UnitGroup.UNITS_LENGTH);
+               rulerUnit = new DoubleModel(0.0, UnitGroup.UNITS_LENGTH);
                rulerUnit.addChangeListener(new ChangeListener() {
                        @Override
                        public void stateChanged(ChangeEvent e) {
@@ -100,10 +109,10 @@ public class ScaleScrollPane extends JScrollPane
                this.setCorner(JScrollPane.UPPER_RIGHT_CORNER, new JPanel());
                this.setCorner(JScrollPane.LOWER_LEFT_CORNER, new JPanel());
                this.setCorner(JScrollPane.LOWER_RIGHT_CORNER, new JPanel());
-
+               
                this.setBorder(BorderFactory.createLineBorder(Color.LIGHT_GRAY));
-
                
+
                viewport = this.getViewport();
                viewport.addMouseListener(this);
                viewport.addMouseMotionListener(this);
@@ -135,14 +144,25 @@ public class ScaleScrollPane extends JScrollPane
        }
        
        
+       /**
+        * Return whether automatic fitting of the figure is allowed.
+        */
        public boolean isFittingAllowed() {
                return allowFit;
        }
        
+       /**
+        * Return whether the figure is currently automatically fitted within the component bounds.
+        */
        public boolean isFitting() {
                return fit;
        }
        
+       /**
+        * Set whether the figure is automatically fitted within the component bounds.
+        * 
+        * @throws BugException         if automatic fitting is disallowed and <code>fit</code> is <code>true</code>
+        */
        public void setFitting(boolean fit) {
                if (fit && !allowFit) {
                        throw new BugException("Attempting to fit figure not allowing fit.");
@@ -169,7 +189,7 @@ public class ScaleScrollPane extends JScrollPane
        public double getScale() {
                return figure.getAbsoluteScale();
        }
-
+       
        public void setScaling(double scale) {
                if (fit) {
                        setFitting(false);
@@ -188,54 +208,54 @@ public class ScaleScrollPane extends JScrollPane
        ////////////////  Mouse handlers  ////////////////
        
 
-       private int dragStartX=0;
-       private int dragStartY=0;
+       private int dragStartX = 0;
+       private int dragStartY = 0;
        private Rectangle dragRectangle = null;
-
+       
        @Override
        public void mousePressed(MouseEvent e) {
                dragStartX = e.getX();
                dragStartY = e.getY();
                dragRectangle = viewport.getViewRect();
        }
-
+       
        @Override
        public void mouseReleased(MouseEvent e) {
                dragRectangle = null;
        }
-
+       
        @Override
        public void mouseDragged(MouseEvent e) {
-               if (dragRectangle==null) {
+               if (dragRectangle == null) {
                        return;
                }
-
-               dragRectangle.setLocation(dragStartX-e.getX(),dragStartY-e.getY());
-
+               
+               dragRectangle.setLocation(dragStartX - e.getX(), dragStartY - e.getY());
+               
                dragStartX = e.getX();
                dragStartY = e.getY();
                
                viewport.scrollRectToVisible(dragRectangle);
        }
-
+       
        @Override
        public void mouseClicked(MouseEvent e) {
        }
-
+       
        @Override
        public void mouseEntered(MouseEvent e) {
        }
-
+       
        @Override
        public void mouseExited(MouseEvent e) {
        }
-
+       
        @Override
        public void mouseMoved(MouseEvent e) {
        }
        
-
        
+
        ////////////////  The view port rulers  ////////////////
        
 
@@ -261,9 +281,9 @@ public class ScaleScrollPane extends JScrollPane
                public void updateSize() {
                        Dimension d = component.getPreferredSize();
                        if (orientation == HORIZONTAL) {
-                               setPreferredSize(new Dimension(d.width+10,RULER_SIZE));
+                               setPreferredSize(new Dimension(d.width + 10, RULER_SIZE));
                        } else {
-                               setPreferredSize(new Dimension(RULER_SIZE,d.height+10));
+                               setPreferredSize(new Dimension(RULER_SIZE, d.height + 10));
                        }
                        revalidate();
                        repaint();
@@ -274,102 +294,102 @@ public class ScaleScrollPane extends JScrollPane
                        if (orientation == HORIZONTAL) {
                                px -= origin.width;
                        } else {
-//                             px = -(px - origin.height);
+                               //                              px = -(px - origin.height);
                                px -= origin.height;
                        }
-                       return px/figure.getAbsoluteScale();
+                       return px / figure.getAbsoluteScale();
                }
                
                private int toPx(double l) {
                        Dimension origin = figure.getOrigin();
-                       int px = (int)(l * figure.getAbsoluteScale() + 0.5);
+                       int px = (int) (l * figure.getAbsoluteScale() + 0.5);
                        if (orientation == HORIZONTAL) {
                                px += origin.width;
                        } else {
                                px = px + origin.height;
-//                             px += origin.height;
+                               //                              px += origin.height;
                        }
                        return px;
                }
                
                
-           @Override
+               @Override
                protected void paintComponent(Graphics g) {
-               super.paintComponent(g);
-                       Graphics2D g2 = (Graphics2D)g;
+                       super.paintComponent(g);
+                       Graphics2D g2 = (Graphics2D) g;
                        
                        Rectangle area = g2.getClipBounds();
-
-               // Fill area with background color
+                       
+                       // Fill area with background color
                        g2.setColor(getBackground());
-               g2.fillRect(area.x, area.y, area.width, area.height+100);
-
+                       g2.fillRect(area.x, area.y, area.width, area.height + 100);
+                       
 
-               int startpx,endpx;
-               if (orientation == HORIZONTAL) {
-                       startpx = area.x;
-                       endpx = area.x+area.width;
-               } else {
-                       startpx = area.y;
-                       endpx = area.y+area.height;
-               }
-               
-               Unit unit = rulerUnit.getCurrentUnit();
-               double start,end,minor,major;
-               start = fromPx(startpx);
-               end = fromPx(endpx);
-               minor = MINOR_TICKS/figure.getAbsoluteScale();
-               major = MAJOR_TICKS/figure.getAbsoluteScale();
+                       int startpx, endpx;
+                       if (orientation == HORIZONTAL) {
+                               startpx = area.x;
+                               endpx = area.x + area.width;
+                       } else {
+                               startpx = area.y;
+                               endpx = area.y + area.height;
+                       }
+                       
+                       Unit unit = rulerUnit.getCurrentUnit();
+                       double start, end, minor, major;
+                       start = fromPx(startpx);
+                       end = fromPx(endpx);
+                       minor = MINOR_TICKS / figure.getAbsoluteScale();
+                       major = MAJOR_TICKS / figure.getAbsoluteScale();
+                       
+                       Tick[] ticks = unit.getTicks(start, end, minor, major);
+                       
 
-               Tick[] ticks = unit.getTicks(start, end, minor, major);
-               
-               
-               // Set color & hints
-               g2.setColor(Color.BLACK);
+                       // Set color & hints
+                       g2.setColor(Color.BLACK);
                        g2.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL,
                                        RenderingHints.VALUE_STROKE_NORMALIZE);
-                       g2.setRenderingHint(RenderingHints.KEY_RENDERING, 
+                       g2.setRenderingHint(RenderingHints.KEY_RENDERING,
                                        RenderingHints.VALUE_RENDER_QUALITY);
                        g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
                                        RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
-
-                       for (Tick t: ticks) {
-                       int position = toPx(t.value);
-                       drawTick(g2,position,t);
-               }
-           }
-           
-           private void drawTick(Graphics g, int position, Tick t) {
-               int length;
-               String str = null;
-               if (t.major) {
-                       length = RULER_SIZE/2;
-               } else {
-                       if (t.notable)
-                               length = RULER_SIZE/3;
-                       else
-                               length = RULER_SIZE/6;
-               }
-               
-               // Set font
-               if (t.major) {
-                       str = rulerUnit.getCurrentUnit().toString(t.value);
-                       if (t.notable)
-                       g.setFont(new Font("SansSerif", Font.BOLD, 9));
-                       else 
-                               g.setFont(new Font("SansSerif", Font.PLAIN, 9));
-               }
-               
-               // Draw tick & text
-               if (orientation == HORIZONTAL) {
-                       g.drawLine(position, RULER_SIZE-length, position, RULER_SIZE);
-                       if (str != null)
-                               g.drawString(str, position, RULER_SIZE-length-1);
-               } else {
-                       g.drawLine(RULER_SIZE-length, position, RULER_SIZE, position);
-                       if (str != null)
-                               g.drawString(str, 1, position-1);
-               }
-           }
+                       
+                       for (Tick t : ticks) {
+                               int position = toPx(t.value);
+                               drawTick(g2, position, t);
+                       }
+               }
+               
+               private void drawTick(Graphics g, int position, Tick t) {
+                       int length;
+                       String str = null;
+                       if (t.major) {
+                               length = RULER_SIZE / 2;
+                       } else {
+                               if (t.notable)
+                                       length = RULER_SIZE / 3;
+                               else
+                                       length = RULER_SIZE / 6;
+                       }
+                       
+                       // Set font
+                       if (t.major) {
+                               str = rulerUnit.getCurrentUnit().toString(t.value);
+                               if (t.notable)
+                                       g.setFont(new Font("SansSerif", Font.BOLD, 9));
+                               else
+                                       g.setFont(new Font("SansSerif", Font.PLAIN, 9));
+                       }
+                       
+                       // Draw tick & text
+                       if (orientation == HORIZONTAL) {
+                               g.drawLine(position, RULER_SIZE - length, position, RULER_SIZE);
+                               if (str != null)
+                                       g.drawString(str, position, RULER_SIZE - length - 1);
+                       } else {
+                               g.drawLine(RULER_SIZE - length, position, RULER_SIZE, position);
+                               if (str != null)
+                                       g.drawString(str, 1, position - 1);
+                       }
+               }
        }
 }
index 3916f6a933d43e6148bbbfcc8aa8a6000488eaac..960325d5f40daab58a290fbea935c1c3769b9945 100644 (file)
@@ -15,9 +15,11 @@ import net.sf.openrocket.util.PinkNoise;
  */\r
 public class PinkNoiseWindModel implements WindModel {\r
        \r
-       /** Source for seed numbers, may be overridden by get/setSeed(). */\r
-       private static final Random seedSource = new Random();\r
+       /** Random value with which to XOR the random seed value */\r
+       private static final int SEED_RANDOMIZATION = 0x7343AA03;\r
        \r
+\r
+\r
        /** Pink noise alpha parameter. */\r
        private static final double ALPHA = 5.0 / 3.0;\r
        \r
@@ -34,7 +36,7 @@ public class PinkNoiseWindModel implements WindModel {
        private double average = 0;\r
        private double standardDeviation = 0;\r
        \r
-       private int seed;\r
+       private final int seed;\r
        \r
        private PinkNoise randomSource = null;\r
        private double time1;\r
@@ -42,12 +44,11 @@ public class PinkNoiseWindModel implements WindModel {
        \r
        \r
        /**\r
-        * Construct a new wind simulator with a random starting seed value.\r
+        * Construct a new wind simulation with a specific seed value.\r
+        * @param seed  the seed value.\r
         */\r
-       public PinkNoiseWindModel() {\r
-               synchronized (seedSource) {\r
-                       seed = seedSource.nextInt();\r
-               }\r
+       public PinkNoiseWindModel(int seed) {\r
+               this.seed = seed ^ SEED_RANDOMIZATION;\r
        }\r
        \r
        \r
@@ -122,18 +123,6 @@ public class PinkNoiseWindModel implements WindModel {
 \r
 \r
 \r
-       public int getSeed() {\r
-               return seed;\r
-       }\r
-       \r
-       public void setSeed(int seed) {\r
-               if (this.seed == seed)\r
-                       return;\r
-               this.seed = seed;\r
-       }\r
-       \r
-       \r
-\r
        @Override\r
        public Coordinate getWindVelocity(double time, double altitude) {\r
                if (time < 0) {\r
index 47dab113ec0cc610fb2b6d87d17177f3e63b2ce0..9b2bece88c405f78e51a0452d7b73acaa2bc5aa1 100644 (file)
@@ -139,7 +139,7 @@ public final class Point {
         */
        public double length() {
                if (length < 0) {
-                       length = Math.sqrt(length2());
+                       length = MathUtil.safeSqrt(length2());
                }
                return length;
        }
index 0e70bf13c239390bba236168b34b00d1bb13ffb8..1f15800f823757c17843f849ff1debe5932e3dd1 100644 (file)
@@ -21,6 +21,8 @@ import net.sf.openrocket.util.Statistics;
  * This is a parallel pattern search optimization algorithm.  The function evaluations are performed
  * using an ExecutorService.  By default a ThreadPoolExecutor is used that has as many thread defined
  * as the system has processors.
+ * <p>
+ * The optimization can be aborted by interrupting the current thread.
  */
 public class MultidirectionalSearchOptimizer implements FunctionOptimizer, Statistics {
        private static final LogHelper log = Application.getLogger();
@@ -30,6 +32,7 @@ public class MultidirectionalSearchOptimizer implements FunctionOptimizer, Stati
        private ParallelFunctionCache functionExecutor;
        
        private boolean useExpansion = false;
+       private boolean useCoordinateSearch = false;
        
        private int stepCount = 0;
        private int reflectionAcceptance = 0;
@@ -73,7 +76,8 @@ public class MultidirectionalSearchOptimizer implements FunctionOptimizer, Stati
                        List<Point> coordinateSearch = new ArrayList<Point>(simplex.size());
                        Point current;
                        double currentValue;
-                       do {
+                       boolean continueOptimization = true;
+                       while (continueOptimization) {
                                
                                log.debug("Starting optimization step with simplex " + simplex +
                                                (simplexComputed ? "" : " (not computed)"));
@@ -95,12 +99,14 @@ public class MultidirectionalSearchOptimizer implements FunctionOptimizer, Stati
                                 * Expansion is unlikely as we're mainly dealing with bounded optimization.
                                 */
                                createReflection(simplex, reflection);
-                               createCoordinateSearch(current, step, coordinateSearch);
+                               if (useCoordinateSearch)
+                                       createCoordinateSearch(current, step, coordinateSearch);
                                if (useExpansion)
                                        createExpansion(simplex, expansion);
                                
                                functionExecutor.compute(reflection);
-                               functionExecutor.compute(coordinateSearch);
+                               if (useCoordinateSearch)
+                                       functionExecutor.compute(coordinateSearch);
                                if (useExpansion)
                                        functionExecutor.compute(expansion);
                                
@@ -113,7 +119,8 @@ public class MultidirectionalSearchOptimizer implements FunctionOptimizer, Stati
                                        log.debug("Reflection was successful, aborting coordinate search, " +
                                                        (useExpansion ? "computing" : "skipping") + " expansion");
                                        
-                                       functionExecutor.abort(coordinateSearch);
+                                       if (useCoordinateSearch)
+                                               functionExecutor.abort(coordinateSearch);
                                        
                                        simplex.clear();
                                        simplex.add(current);
@@ -160,24 +167,31 @@ public class MultidirectionalSearchOptimizer implements FunctionOptimizer, Stati
                                         */
                                        halveStep(simplex);
                                        functionExecutor.compute(simplex);
-                                       functionExecutor.waitFor(coordinateSearch);
                                        
-                                       if (accept(coordinateSearch, currentValue)) {
+                                       if (useCoordinateSearch) {
+                                               functionExecutor.waitFor(coordinateSearch);
                                                
-                                               log.debug("Coordinate search successful, reseting simplex");
-                                               List<Point> toAbort = new LinkedList<Point>(simplex);
-                                               simplex.clear();
-                                               simplex.add(current);
-                                               for (Point p : pattern) {
-                                                       simplex.add(current.add(p.mul(step)));
+                                               if (accept(coordinateSearch, currentValue)) {
+                                                       
+                                                       log.debug("Coordinate search successful, reseting simplex");
+                                                       List<Point> toAbort = new LinkedList<Point>(simplex);
+                                                       simplex.clear();
+                                                       simplex.add(current);
+                                                       for (Point p : pattern) {
+                                                               simplex.add(current.add(p.mul(step)));
+                                                       }
+                                                       toAbort.removeAll(simplex);
+                                                       functionExecutor.abort(toAbort);
+                                                       simplexComputed = false;
+                                                       coordinateAcceptance++;
+                                                       
+                                               } else {
+                                                       log.debug("Coordinate search unsuccessful, halving step.");
+                                                       step /= 2;
+                                                       reductionFallback++;
                                                }
-                                               toAbort.removeAll(simplex);
-                                               functionExecutor.abort(toAbort);
-                                               simplexComputed = false;
-                                               coordinateAcceptance++;
-                                               
                                        } else {
-                                               log.debug("Coordinate search unsuccessful, halving step.");
+                                               log.debug("Coordinate search not used, halving step.");
                                                step /= 2;
                                                reductionFallback++;
                                        }
@@ -186,12 +200,14 @@ public class MultidirectionalSearchOptimizer implements FunctionOptimizer, Stati
                                
                                log.debug("Ending optimization step with simplex " + simplex);
                                
+                               continueOptimization = control.stepTaken(current, currentValue, simplex.get(0),
+                                               functionExecutor.getValue(simplex.get(0)), step);
+                               
                                if (Thread.interrupted()) {
                                        throw new InterruptedException();
                                }
                                
-                       } while (control.stepTaken(current, currentValue, simplex.get(0),
-                                       functionExecutor.getValue(simplex.get(0)), step));
+                       }
                        
                } catch (InterruptedException e) {
                        log.info("Optimization was interrupted with InterruptedException");
index 592f4eaa02012e34bb01b62e5466677c0c2ed865..1a7df3331f203fea437a6b40ecfb5a53b354448b 100644 (file)
@@ -58,7 +58,7 @@ public class SearchPattern {
                        for (int j = 0; j < i; j++) {
                                value -= MathUtil.pow2(coordinates[j]);
                        }
-                       value = Math.sqrt(value);
+                       value = MathUtil.safeSqrt(value);
                        
                        coordinates[i] = value;
                        pattern.add(new Point(coordinates));
index e8506e8d684bb0dc74817a201b8966687ce56be9..1a8f3ff115ca8732c3b4b2b5ee5040709aa4dd1e 100644 (file)
@@ -2,6 +2,7 @@ package net.sf.openrocket.optimization.rocketoptimization;
 
 import net.sf.openrocket.document.Simulation;
 import net.sf.openrocket.optimization.general.OptimizationException;
+import net.sf.openrocket.unit.UnitGroup;
 
 /**
  * A parameter of a rocket or simulation that can be optimized
@@ -30,4 +31,11 @@ public interface OptimizableParameter {
         */
        public double computeValue(Simulation simulation) throws OptimizationException;
        
+       
+       /**
+        * Return the unit group associated with the computed value.
+        * @return      the unit group of the computed value.
+        */
+       public UnitGroup getUnitGroup();
+       
 }
index b0e8e8430b5094a6457c0ce2ef03ec057ed38a8e..2f224269fd84d422ac50a4db2d985039452619c6 100644 (file)
@@ -67,16 +67,13 @@ public class RocketOptimizationFunction implements Function {
        @Override
        public double evaluate(Point point) throws InterruptedException, OptimizationException {
                
-               System.out.println("Evaluating function at point " + point);
-               
                /*
                 * parameterValue is the computed parameter value (e.g. altitude)
                 * goalValue is the value that needs to be minimized
                 */
                double goalValue, parameterValue;
                
-
-               log.verbose("Computing optimization function value at point " + point);
+               log.debug("Computing optimization function value at point " + point);
                
                // Create the new simulation based on the point
                double[] p = point.asArray();
@@ -92,19 +89,18 @@ public class RocketOptimizationFunction implements Function {
                
 
                // Check whether the point is within the simulation domain
-               Pair<Double, Double> d = domain.getDistanceToDomain(simulation);
+               Pair<Double, Value> d = domain.getDistanceToDomain(simulation);
                double distance = d.getU();
-               double referenceValue = d.getV();
+               Value referenceValue = d.getV();
                if (distance > 0 || Double.isNaN(distance)) {
                        if (Double.isNaN(distance)) {
                                goalValue = Double.MAX_VALUE;
                        } else {
                                goalValue = (distance + 1) * OUTSIDE_DOMAIN_SCALE;
                        }
-                       log.verbose("Optimization point is outside of domain, distance=" + distance + " goal function value=" + goalValue);
-                       System.out.println("Optimization point is outside of domain, distance=" + distance + " goal function value=" + goalValue);
+                       log.debug("Optimization point is outside of domain, distance=" + distance + " goal function value=" + goalValue);
                        
-                       fireEvent(simulation, point, referenceValue, Double.NaN, goalValue);
+                       fireEvent(simulation, point, referenceValue, null, goalValue);
                        
                        return goalValue;
                }
@@ -122,9 +118,9 @@ public class RocketOptimizationFunction implements Function {
                }
                
                log.verbose("Parameter value at point " + point + " is " + parameterValue + ", goal function value=" + goalValue);
-               System.out.println("Parameter value at point " + point + " is " + parameterValue + ", goal function value=" + goalValue);
                
-               fireEvent(simulation, point, referenceValue, parameterValue, goalValue);
+               fireEvent(simulation, point, referenceValue, new Value(parameterValue, parameter.getUnitGroup().getDefaultUnit()),
+                               goalValue);
                
                return goalValue;
        }
@@ -168,7 +164,7 @@ public class RocketOptimizationFunction implements Function {
        
        
 
-       private void fireEvent(Simulation simulation, Point p, double domainReference, double parameterValue, double goalValue)
+       private void fireEvent(Simulation simulation, Point p, Value domainReference, Value parameterValue, double goalValue)
                        throws OptimizationException {
                
                if (listeners.isEmpty()) {
index dac89c059e56d18af6c9daa32bf6c3663b6162ac..05fc429bf2e96809f66cfe728dc1ceebfbdb90a4 100644 (file)
@@ -14,11 +14,11 @@ public interface RocketOptimizationListener {
         * Called after successful function evaluation.
         * 
         * @param point                         the optimization point.
-        * @param state                         the values to which the rocket has been modified, in the order of "point".
-        * @param domainReference       the domain reference value (or NaN if unavailable)
+        * @param state                         the values to which the rocket has been modified in SI units, in the order of "point".
+        * @param domainReference       the domain reference description (or null if unavailable)
         * @param parameterValue        the parameter value (or NaN if unavailable)
         * @param goalValue                     the goal value (return value of the function)
         */
-       public void evaluated(Point point, Value[] state, double domainReference, double parameterValue, double goalValue);
+       public void evaluated(Point point, Value[] state, Value domainReference, Value parameterValue, double goalValue);
        
 }
index d97eaae4738e8ef151b7d77e540d3e7cecd8354e..d2a204ec5afafb4092b6d2be7c20a15f328eb748 100644 (file)
@@ -1,6 +1,7 @@
 package net.sf.openrocket.optimization.rocketoptimization;
 
 import net.sf.openrocket.document.Simulation;
+import net.sf.openrocket.unit.Value;
 import net.sf.openrocket.util.Pair;
 
 /**
@@ -18,11 +19,12 @@ public interface SimulationDomain {
         * is NaN, the simulation is outside of the domain.
         * 
         * @param simulation    the simulation to check.
-        * @return                              the double parameter is the domain indication;
+        * @return                              A pair indicating the status.  The first element is the reference value,
         *                                              a negative value or zero if the simulation is in the domain,
-        *                                              a positive value or NaN if not.  The second is a human-readable
+        *                                              a positive value or NaN if not.  The second is the value
         *                                              indication of the domain (may be null).
         */
-       public Pair<Double, Double> getDistanceToDomain(Simulation simulation);
+       public Pair<Double, Value> getDistanceToDomain(Simulation simulation);
        
+
 }
index 3efd5d6d758a57b99447ee9e1708032313a8c5fb..1989ea074eb0ad216dda28a7032bbea93f6907a5 100644 (file)
@@ -17,11 +17,16 @@ import net.sf.openrocket.util.ChangeSource;
 public interface SimulationModifier extends ChangeSource {
        
        /**
-        * Return a name describing this modifier.
+        * Return a short name describing this modifier.
         * @return      a name describing this modifier.
         */
        public String getName();
        
+       /**
+        * Return a longer description describing this modifiers.
+        * @return      a description of the modifier.
+        */
+       public String getDescription();
        
        /**
         * Return the object this modifier is related to.  This is for example the
@@ -94,4 +99,13 @@ public interface SimulationModifier extends ChangeSource {
         */
        public void modify(Simulation simulation, double scaledValue) throws OptimizationException;
        
+       
+       /**
+        * Compare whether this SimulationModifier is equivalent to another simulation modifier.
+        * "Equivalent" means that the simulation modifier corresponds to the same modification in
+        * another rocket instance (e.g. the same modification on another rocket component that
+        * has the same component ID).
+        */
+       @Override
+       public boolean equals(Object obj);
 }
index 2f398be39be7c798d0d942c97893c184503e378f..fac678eae56c93487539f93346ba3c90cd6e44a6 100644 (file)
@@ -2,6 +2,7 @@ package net.sf.openrocket.optimization.rocketoptimization.domains;
 
 import net.sf.openrocket.document.Simulation;
 import net.sf.openrocket.optimization.rocketoptimization.SimulationDomain;
+import net.sf.openrocket.unit.Value;
 import net.sf.openrocket.util.Pair;
 
 /**
@@ -12,8 +13,8 @@ import net.sf.openrocket.util.Pair;
 public class IdentitySimulationDomain implements SimulationDomain {
        
        @Override
-       public Pair<Double, Double> getDistanceToDomain(Simulation simulation) {
-               return new Pair<Double, Double>(-1.0, Double.NaN);
+       public Pair<Double, Value> getDistanceToDomain(Simulation simulation) {
+               return new Pair<Double, Value>(-1.0, null);
        }
        
 }
index 0142368d8f684e235ca807b471b05b233107374f..143ef29ae251691f7d2714f9fdf727bb47c3add0 100644 (file)
@@ -11,37 +11,53 @@ import net.sf.openrocket.optimization.rocketoptimization.SimulationDomain;
 import net.sf.openrocket.rocketcomponent.Configuration;
 import net.sf.openrocket.rocketcomponent.RocketComponent;
 import net.sf.openrocket.rocketcomponent.SymmetricComponent;
+import net.sf.openrocket.unit.UnitGroup;
+import net.sf.openrocket.unit.Value;
 import net.sf.openrocket.util.Coordinate;
 import net.sf.openrocket.util.MathUtil;
 import net.sf.openrocket.util.Pair;
 import net.sf.openrocket.util.Prefs;
 
 /**
- * A simulation domain that limits the requires stability of the rocket.
+ * A simulation domain that limits the required stability of the rocket.
  * 
  * @author Sampo Niskanen <sampo.niskanen@iki.fi>
  */
 public class StabilityDomain implements SimulationDomain {
        
-       /*
-        * TODO: HIGH:  Should this rather inspect stability during flight
-        */
-
-       private final double limit;
-       private final boolean absolute;
+       private final double minimum;
+       private final boolean minAbsolute;
+       private final double maximum;
+       private final boolean maxAbsolute;
        
        
-       public StabilityDomain(double limit, boolean absolute) {
-               this.limit = limit;
-               this.absolute = absolute;
+       /**
+        * Sole constructor.
+        * 
+        * @param minimum               minimum stability requirement (or <code>NaN</code> for no limit)
+        * @param minAbsolute   <code>true</code> if minimum is an absolute SI measurement,
+        *                                              <code>false</code> if it is relative to the rocket caliber
+        * @param maximum               maximum stability requirement (or <code>NaN</code> for no limit)
+        * @param maxAbsolute   <code>true</code> if maximum is an absolute SI measurement,
+        *                                              <code>false</code> if it is relative to the rocket caliber
+        */
+       public StabilityDomain(double minimum, boolean minAbsolute, double maximum, boolean maxAbsolute) {
+               super();
+               this.minimum = minimum;
+               this.minAbsolute = minAbsolute;
+               this.maximum = maximum;
+               this.maxAbsolute = maxAbsolute;
        }
        
        
+
+
        @Override
-       public Pair<Double, Double> getDistanceToDomain(Simulation simulation) {
+       public Pair<Double, Value> getDistanceToDomain(Simulation simulation) {
                Coordinate cp, cg;
                double cpx, cgx;
-               double reference;
+               double absolute;
+               double relative;
                
                /*
                 * These are instantiated each time because this class must be thread-safe.
@@ -73,23 +89,51 @@ public class StabilityDomain implements SimulationDomain {
                
 
                // Calculate the reference (absolute or relative)
-               reference = cpx - cgx;
-               if (!absolute) {
-                       double diameter = 0;
-                       for (RocketComponent c : configuration) {
-                               if (c instanceof SymmetricComponent) {
-                                       double d1 = ((SymmetricComponent) c).getForeRadius() * 2;
-                                       double d2 = ((SymmetricComponent) c).getAftRadius() * 2;
-                                       diameter = MathUtil.max(diameter, d1, d2);
-                               }
+               absolute = cpx - cgx;
+               
+               double diameter = 0;
+               for (RocketComponent c : configuration) {
+                       if (c instanceof SymmetricComponent) {
+                               double d1 = ((SymmetricComponent) c).getForeRadius() * 2;
+                               double d2 = ((SymmetricComponent) c).getAftRadius() * 2;
+                               diameter = MathUtil.max(diameter, d1, d2);
                        }
-                       
-                       reference = (cpx - cgx) / diameter;
                }
+               relative = absolute / diameter;
                
-               System.out.println("DOMAIN: limit=" + limit + " reference=" + reference + " result=" + (limit - reference));
+
+               Value desc;
+               if (minAbsolute && maxAbsolute) {
+                       desc = new Value(absolute, UnitGroup.UNITS_LENGTH);
+               } else {
+                       desc = new Value(relative, UnitGroup.UNITS_STABILITY_CALIBERS);
+               }
                
-               return new Pair<Double, Double>(limit - reference, reference);
+               double ref;
+               if (minAbsolute) {
+                       ref = minimum - absolute;
+                       if (ref > 0) {
+                               return new Pair<Double, Value>(ref, desc);
+                       }
+               } else {
+                       ref = minimum - relative;
+                       if (ref > 0) {
+                               return new Pair<Double, Value>(ref, desc);
+                       }
+               }
+               
+               if (maxAbsolute) {
+                       ref = absolute - maximum;
+                       if (ref > 0) {
+                               return new Pair<Double, Value>(ref, desc);
+                       }
+               } else {
+                       ref = relative - maximum;
+                       if (ref > 0) {
+                               return new Pair<Double, Value>(ref, desc);
+                       }
+               }
+               
+               return new Pair<Double, Value>(0.0, desc);
        }
-       
 }
index 5d24e44675c763427690c1ba92235b7c4631f7e7..f680dda70d720c6ed587d168dee3c3c4e7a1bcb5 100644 (file)
@@ -22,6 +22,7 @@ import net.sf.openrocket.util.MathUtil;
 public abstract class AbstractSimulationModifier implements SimulationModifier {
        
        private final String name;
+       private final String description;
        private final Object relatedObject;
        private final UnitGroup unitGroup;
        
@@ -34,14 +35,23 @@ public abstract class AbstractSimulationModifier implements SimulationModifier {
        /**
         * Sole constructor.
         * 
-        * @param modifierName          the name of this modifier (returned by {@link #getName()})
-        * @param relatedObject         the related object (returned by {@link #getRelatedObject()})
-        * @param unitGroup                     the unit group (returned by {@link #getUnitGroup()})
+        * @param modifierName                  the name of this modifier (returned by {@link #getName()})
+        * @param modifierDescription   the description of this modifier (returned by {@link #getDescription()})
+        * @param relatedObject                 the related object (returned by {@link #getRelatedObject()})
+        * @param unitGroup                             the unit group (returned by {@link #getUnitGroup()})
         */
-       public AbstractSimulationModifier(String modifierName, Object relatedObject, UnitGroup unitGroup) {
+       public AbstractSimulationModifier(String modifierName, String modifierDescription, Object relatedObject,
+                       UnitGroup unitGroup) {
                this.name = modifierName;
+               this.description = modifierDescription;
                this.relatedObject = relatedObject;
                this.unitGroup = unitGroup;
+               
+               if (this.name == null || this.description == null || this.relatedObject == null || this.unitGroup == null) {
+                       throw new IllegalArgumentException("null value provided:" +
+                                       " name=" + this.name + " description=" + description + " relatedObject=" + relatedObject +
+                                       " unitGroup=" + unitGroup);
+               }
        }
        
        
@@ -50,6 +60,11 @@ public abstract class AbstractSimulationModifier implements SimulationModifier {
                return name;
        }
        
+       @Override
+       public String getDescription() {
+               return description;
+       }
+       
        @Override
        public Object getRelatedObject() {
                return relatedObject;
@@ -155,4 +170,40 @@ public abstract class AbstractSimulationModifier implements SimulationModifier {
                }
        }
        
+       @Override
+       public boolean equals(Object obj) {
+               if (this == obj)
+                       return true;
+               if (obj == null)
+                       return false;
+               if (getClass() != obj.getClass())
+                       return false;
+               
+               AbstractSimulationModifier other = (AbstractSimulationModifier) obj;
+               if (!this.description.equals(other.description))
+                       return false;
+               if (!this.name.equals(other.name))
+                       return false;
+               if (!this.relatedObject.equals(other.relatedObject))
+                       return false;
+               if (!this.unitGroup.equals(other.unitGroup))
+                       return false;
+               
+               return true;
+       }
+       
+       
+
+       @Override
+       public int hashCode() {
+               final int prime = 31;
+               int result = 1;
+               result = prime * result + (description.hashCode());
+               result = prime * result + (name.hashCode());
+               result = prime * result + (relatedObject.hashCode());
+               result = prime * result + (unitGroup.hashCode());
+               return result;
+       }
+       
+
 }
index b22d9332945a517e6b34ab41f50826a0da465bc7..bd80889a892339ea5b1edf22e2b64c9391b67182 100644 (file)
@@ -19,18 +19,19 @@ public class GenericComponentModifier extends GenericModifier<RocketComponent> {
        /**
         * Sole constructor.
         * 
-        * @param modifierName          the name of this modifier (returned by {@link #getName()})
-        * @param relatedObject         the related object (returned by {@link #getRelatedObject()})
-        * @param unitGroup                     the unit group (returned by {@link #getUnitGroup()})
-        * @param multiplier            the multiplier by which the value returned by the getter is multiplied
-        *                                                      to obtain the desired value
-        * @param componentClass        the RocketComponent class type that is being modified
-        * @param componentId           the ID of the component to modify
-        * @param methodName            the base name of the getter/setter methods (without "get"/"set")
+        * @param modifierName                  the name of this modifier (returned by {@link #getName()})
+        * @param modifierDescription   the description of this modifier (returned by {@link #getDescription()})
+        * @param relatedObject                 the related object (returned by {@link #getRelatedObject()})
+        * @param unitGroup                             the unit group (returned by {@link #getUnitGroup()})
+        * @param multiplier                    the multiplier by which the value returned by the getter is multiplied
+        *                                                              to obtain the desired value
+        * @param componentClass                the RocketComponent class type that is being modified
+        * @param componentId                   the ID of the component to modify
+        * @param methodName                    the base name of the getter/setter methods (without "get"/"set")
         */
-       public GenericComponentModifier(String modifierName, Object relatedObject, UnitGroup unitGroup,
+       public GenericComponentModifier(String modifierName, String modifierDescription, Object relatedObject, UnitGroup unitGroup,
                        double multiplier, Class<? extends RocketComponent> componentClass, String componentId, String methodName) {
-               super(modifierName, relatedObject, unitGroup, multiplier, componentClass, methodName);
+               super(modifierName, modifierDescription, relatedObject, unitGroup, multiplier, componentClass, methodName);
                
                this.componentClass = componentClass;
                this.componentId = componentId;
index f2cb67f77b0a43d52579fc8337cfd4a675aca3d4..ec931108d5297ee44f5b2891f7b5208db54f28a0 100644 (file)
@@ -29,6 +29,7 @@ public abstract class GenericModifier<T> extends AbstractSimulationModifier {
         * Sole constructor.
         * 
         * @param modifierName          the name of this modifier (returned by {@link #getName()})
+        * @param modifierDescription   the description of this modifier (returned by {@link #getDescription()})
         * @param relatedObject         the related object (returned by {@link #getRelatedObject()})
         * @param unitGroup                     the unit group (returned by {@link #getUnitGroup()})
         * @param multiplier            the multiplier by which the value returned by the getter is multiplied
@@ -36,9 +37,9 @@ public abstract class GenericModifier<T> extends AbstractSimulationModifier {
         * @param modifiedClass         the class type that {@link #getModifiedObject(Simulation)} returns
         * @param methodName            the base name of the getter/setter methods (without "get"/"set")
         */
-       public GenericModifier(String modifierName, Object relatedObject, UnitGroup unitGroup, double multiplier,
-                       Class<? extends T> modifiedClass, String methodName) {
-               super(modifierName, relatedObject, unitGroup);
+       public GenericModifier(String modifierName, String modifierDescription, Object relatedObject, UnitGroup unitGroup,
+                       double multiplier, Class<? extends T> modifiedClass, String methodName) {
+               super(modifierName, modifierDescription, relatedObject, unitGroup);
                this.multiplier = multiplier;
                
                this.modifiedClass = modifiedClass;
diff --git a/src/net/sf/openrocket/optimization/rocketoptimization/parameters/GroundHitVelocityParameter.java b/src/net/sf/openrocket/optimization/rocketoptimization/parameters/GroundHitVelocityParameter.java
new file mode 100644 (file)
index 0000000..431e9ae
--- /dev/null
@@ -0,0 +1,55 @@
+package net.sf.openrocket.optimization.rocketoptimization.parameters;
+
+import net.sf.openrocket.document.Simulation;
+import net.sf.openrocket.l10n.Translator;
+import net.sf.openrocket.logging.LogHelper;
+import net.sf.openrocket.optimization.general.OptimizationException;
+import net.sf.openrocket.optimization.rocketoptimization.OptimizableParameter;
+import net.sf.openrocket.simulation.FlightDataType;
+import net.sf.openrocket.simulation.exception.MotorIgnitionException;
+import net.sf.openrocket.simulation.exception.SimulationException;
+import net.sf.openrocket.simulation.exception.SimulationLaunchException;
+import net.sf.openrocket.startup.Application;
+import net.sf.openrocket.unit.UnitGroup;
+
+/**
+ * An optimization parameter that computes the speed the rocket hits the ground.
+ * 
+ * @author Sampo Niskanen <sampo.niskanen@iki.fi>
+ */
+public class GroundHitVelocityParameter implements OptimizableParameter {
+       
+       private static final LogHelper log = Application.getLogger();
+       private static final Translator trans = Application.getTranslator();
+       
+       @Override
+       public String getName() {
+               return trans.get("name");
+       }
+       
+       @Override
+       public double computeValue(Simulation simulation) throws OptimizationException {
+               try {
+                       log.debug("Running simulation to evaluate ground hit speed");
+                       simulation.simulate();
+                       double value = simulation.getSimulatedData().getBranch(0).getLast(FlightDataType.TYPE_VELOCITY_TOTAL);
+                       log.debug("Ground hit speed was " + value);
+                       return value;
+               } catch (MotorIgnitionException e) {
+                       // A problem with motor ignition will cause optimization to fail
+                       throw new OptimizationException(e);
+               } catch (SimulationLaunchException e) {
+                       // Other launch exceptions result in zero altitude
+                       return Double.NaN;
+               } catch (SimulationException e) {
+                       // Other exceptions fail
+                       throw new OptimizationException(e);
+               }
+       }
+       
+       @Override
+       public UnitGroup getUnitGroup() {
+               return UnitGroup.UNITS_VELOCITY;
+       }
+       
+}
diff --git a/src/net/sf/openrocket/optimization/rocketoptimization/parameters/LandingDistanceParameter.java b/src/net/sf/openrocket/optimization/rocketoptimization/parameters/LandingDistanceParameter.java
new file mode 100644 (file)
index 0000000..4d6f571
--- /dev/null
@@ -0,0 +1,55 @@
+package net.sf.openrocket.optimization.rocketoptimization.parameters;
+
+import net.sf.openrocket.document.Simulation;
+import net.sf.openrocket.l10n.Translator;
+import net.sf.openrocket.logging.LogHelper;
+import net.sf.openrocket.optimization.general.OptimizationException;
+import net.sf.openrocket.optimization.rocketoptimization.OptimizableParameter;
+import net.sf.openrocket.simulation.FlightDataType;
+import net.sf.openrocket.simulation.exception.MotorIgnitionException;
+import net.sf.openrocket.simulation.exception.SimulationException;
+import net.sf.openrocket.simulation.exception.SimulationLaunchException;
+import net.sf.openrocket.startup.Application;
+import net.sf.openrocket.unit.UnitGroup;
+
+/**
+ * An optimization parameter that computes the distance where a rocket lands.
+ * 
+ * @author Sampo Niskanen <sampo.niskanen@iki.fi>
+ */
+public class LandingDistanceParameter implements OptimizableParameter {
+       
+       private static final LogHelper log = Application.getLogger();
+       private static final Translator trans = Application.getTranslator();
+       
+       @Override
+       public String getName() {
+               return trans.get("name");
+       }
+       
+       @Override
+       public double computeValue(Simulation simulation) throws OptimizationException {
+               try {
+                       log.debug("Running simulation to evaluate rocket landing distance");
+                       simulation.simulate();
+                       double value = simulation.getSimulatedData().getBranch(0).getLast(FlightDataType.TYPE_POSITION_XY);
+                       log.debug("Landing distance was " + value);
+                       return value;
+               } catch (MotorIgnitionException e) {
+                       // A problem with motor ignition will cause optimization to fail
+                       throw new OptimizationException(e);
+               } catch (SimulationLaunchException e) {
+                       // Other launch exceptions result in zero altitude
+                       return Double.NaN;
+               } catch (SimulationException e) {
+                       // Other exceptions fail
+                       throw new OptimizationException(e);
+               }
+       }
+       
+       @Override
+       public UnitGroup getUnitGroup() {
+               return UnitGroup.UNITS_DISTANCE;
+       }
+       
+}
diff --git a/src/net/sf/openrocket/optimization/rocketoptimization/parameters/MaximumAccelerationParameter.java b/src/net/sf/openrocket/optimization/rocketoptimization/parameters/MaximumAccelerationParameter.java
new file mode 100644 (file)
index 0000000..51f73d8
--- /dev/null
@@ -0,0 +1,56 @@
+package net.sf.openrocket.optimization.rocketoptimization.parameters;
+
+import net.sf.openrocket.document.Simulation;
+import net.sf.openrocket.l10n.Translator;
+import net.sf.openrocket.logging.LogHelper;
+import net.sf.openrocket.optimization.general.OptimizationException;
+import net.sf.openrocket.optimization.rocketoptimization.OptimizableParameter;
+import net.sf.openrocket.simulation.FlightDataType;
+import net.sf.openrocket.simulation.exception.MotorIgnitionException;
+import net.sf.openrocket.simulation.exception.SimulationException;
+import net.sf.openrocket.simulation.exception.SimulationLaunchException;
+import net.sf.openrocket.simulation.listeners.system.ApogeeEndListener;
+import net.sf.openrocket.startup.Application;
+import net.sf.openrocket.unit.UnitGroup;
+
+/**
+ * An optimization parameter that computes the maximum acceleration during a simulated flight.
+ * 
+ * @author Sampo Niskanen <sampo.niskanen@iki.fi>
+ */
+public class MaximumAccelerationParameter implements OptimizableParameter {
+       
+       private static final LogHelper log = Application.getLogger();
+       private static final Translator trans = Application.getTranslator();
+       
+       @Override
+       public String getName() {
+               return trans.get("name");
+       }
+       
+       @Override
+       public double computeValue(Simulation simulation) throws OptimizationException {
+               try {
+                       log.debug("Running simulation to evaluate maximum acceleration");
+                       simulation.simulate(new ApogeeEndListener());
+                       double value = simulation.getSimulatedData().getBranch(0).getMaximum(FlightDataType.TYPE_ACCELERATION_TOTAL);
+                       log.debug("Maximum acceleration was " + value);
+                       return value;
+               } catch (MotorIgnitionException e) {
+                       // A problem with motor ignition will cause optimization to fail
+                       throw new OptimizationException(e);
+               } catch (SimulationLaunchException e) {
+                       // Other launch exceptions result in zero velocity
+                       return Double.NaN;
+               } catch (SimulationException e) {
+                       // Other exceptions fail
+                       throw new OptimizationException(e);
+               }
+       }
+       
+       @Override
+       public UnitGroup getUnitGroup() {
+               return UnitGroup.UNITS_ACCELERATION;
+       }
+       
+}
index da40a6761e50d4875336cb2c0087b285bfab6fec..248d1cf9cf781d2dba7bca2812bbe278b9fad3e4 100644 (file)
@@ -1,11 +1,17 @@
 package net.sf.openrocket.optimization.rocketoptimization.parameters;
 
 import net.sf.openrocket.document.Simulation;
+import net.sf.openrocket.l10n.Translator;
+import net.sf.openrocket.logging.LogHelper;
 import net.sf.openrocket.optimization.general.OptimizationException;
 import net.sf.openrocket.optimization.rocketoptimization.OptimizableParameter;
 import net.sf.openrocket.simulation.FlightDataType;
+import net.sf.openrocket.simulation.exception.MotorIgnitionException;
 import net.sf.openrocket.simulation.exception.SimulationException;
+import net.sf.openrocket.simulation.exception.SimulationLaunchException;
 import net.sf.openrocket.simulation.listeners.system.ApogeeEndListener;
+import net.sf.openrocket.startup.Application;
+import net.sf.openrocket.unit.UnitGroup;
 
 /**
  * An optimization parameter that computes the maximum altitude of a simulated flight.
@@ -14,21 +20,36 @@ import net.sf.openrocket.simulation.listeners.system.ApogeeEndListener;
  */
 public class MaximumAltitudeParameter implements OptimizableParameter {
        
+       private static final LogHelper log = Application.getLogger();
+       private static final Translator trans = Application.getTranslator();
+       
        @Override
        public String getName() {
-               return "Maximum altitude";
+               return trans.get("name");
        }
        
        @Override
        public double computeValue(Simulation simulation) throws OptimizationException {
                try {
-                       System.out.println("Running simulation");
+                       log.debug("Running simulation to evaluate apogee altitude");
                        simulation.simulate(new ApogeeEndListener());
-                       System.out.println("Maximum altitude was " + simulation.getSimulatedData().getBranch(0).getMaximum(FlightDataType.TYPE_ALTITUDE));
+                       log.debug("Maximum altitude was " + simulation.getSimulatedData().getBranch(0).getMaximum(FlightDataType.TYPE_ALTITUDE));
                        return simulation.getSimulatedData().getBranch(0).getMaximum(FlightDataType.TYPE_ALTITUDE);
+               } catch (MotorIgnitionException e) {
+                       // A problem with motor ignition will cause optimization to fail
+                       throw new OptimizationException(e);
+               } catch (SimulationLaunchException e) {
+                       // Other launch exceptions result in zero altitude
+                       return 0.0;
                } catch (SimulationException e) {
+                       // Other exceptions fail
                        throw new OptimizationException(e);
                }
        }
        
+       @Override
+       public UnitGroup getUnitGroup() {
+               return UnitGroup.UNITS_DISTANCE;
+       }
+       
 }
diff --git a/src/net/sf/openrocket/optimization/rocketoptimization/parameters/MaximumVelocityParameter.java b/src/net/sf/openrocket/optimization/rocketoptimization/parameters/MaximumVelocityParameter.java
new file mode 100644 (file)
index 0000000..58c7a27
--- /dev/null
@@ -0,0 +1,56 @@
+package net.sf.openrocket.optimization.rocketoptimization.parameters;
+
+import net.sf.openrocket.document.Simulation;
+import net.sf.openrocket.l10n.Translator;
+import net.sf.openrocket.logging.LogHelper;
+import net.sf.openrocket.optimization.general.OptimizationException;
+import net.sf.openrocket.optimization.rocketoptimization.OptimizableParameter;
+import net.sf.openrocket.simulation.FlightDataType;
+import net.sf.openrocket.simulation.exception.MotorIgnitionException;
+import net.sf.openrocket.simulation.exception.SimulationException;
+import net.sf.openrocket.simulation.exception.SimulationLaunchException;
+import net.sf.openrocket.simulation.listeners.system.ApogeeEndListener;
+import net.sf.openrocket.startup.Application;
+import net.sf.openrocket.unit.UnitGroup;
+
+/**
+ * An optimization parameter that computes the maximum velocity during a simulated flight.
+ * 
+ * @author Sampo Niskanen <sampo.niskanen@iki.fi>
+ */
+public class MaximumVelocityParameter implements OptimizableParameter {
+       
+       private static final LogHelper log = Application.getLogger();
+       private static final Translator trans = Application.getTranslator();
+       
+       @Override
+       public String getName() {
+               return trans.get("name");
+       }
+       
+       @Override
+       public double computeValue(Simulation simulation) throws OptimizationException {
+               try {
+                       log.debug("Running simulation to evaluate maximum velocity");
+                       simulation.simulate(new ApogeeEndListener());
+                       double value = simulation.getSimulatedData().getBranch(0).getMaximum(FlightDataType.TYPE_VELOCITY_TOTAL);
+                       log.debug("Maximum velocity was " + value);
+                       return value;
+               } catch (MotorIgnitionException e) {
+                       // A problem with motor ignition will cause optimization to fail
+                       throw new OptimizationException(e);
+               } catch (SimulationLaunchException e) {
+                       // Other launch exceptions result in zero velocity
+                       return Double.NaN;
+               } catch (SimulationException e) {
+                       // Other exceptions fail
+                       throw new OptimizationException(e);
+               }
+       }
+       
+       @Override
+       public UnitGroup getUnitGroup() {
+               return UnitGroup.UNITS_VELOCITY;
+       }
+       
+}
diff --git a/src/net/sf/openrocket/optimization/rocketoptimization/parameters/StabilityParameter.java b/src/net/sf/openrocket/optimization/rocketoptimization/parameters/StabilityParameter.java
new file mode 100644 (file)
index 0000000..a9f099c
--- /dev/null
@@ -0,0 +1,111 @@
+package net.sf.openrocket.optimization.rocketoptimization.parameters;
+
+import net.sf.openrocket.aerodynamics.AerodynamicCalculator;
+import net.sf.openrocket.aerodynamics.BarrowmanCalculator;
+import net.sf.openrocket.aerodynamics.FlightConditions;
+import net.sf.openrocket.document.Simulation;
+import net.sf.openrocket.l10n.Translator;
+import net.sf.openrocket.logging.LogHelper;
+import net.sf.openrocket.masscalc.BasicMassCalculator;
+import net.sf.openrocket.masscalc.MassCalculator;
+import net.sf.openrocket.masscalc.MassCalculator.MassCalcType;
+import net.sf.openrocket.optimization.general.OptimizationException;
+import net.sf.openrocket.optimization.rocketoptimization.OptimizableParameter;
+import net.sf.openrocket.rocketcomponent.Configuration;
+import net.sf.openrocket.rocketcomponent.RocketComponent;
+import net.sf.openrocket.rocketcomponent.SymmetricComponent;
+import net.sf.openrocket.startup.Application;
+import net.sf.openrocket.unit.UnitGroup;
+import net.sf.openrocket.util.Coordinate;
+import net.sf.openrocket.util.MathUtil;
+import net.sf.openrocket.util.Prefs;
+
+/**
+ * An optimization parameter that computes either the absolute or relative stability of a rocket.
+ * 
+ * @author Sampo Niskanen <sampo.niskanen@iki.fi>
+ */
+public class StabilityParameter implements OptimizableParameter {
+       
+       private static final LogHelper log = Application.getLogger();
+       private static final Translator trans = Application.getTranslator();
+       
+
+       private final boolean absolute;
+       
+       public StabilityParameter(boolean absolute) {
+               this.absolute = absolute;
+       }
+       
+       
+       @Override
+       public String getName() {
+               return trans.get("name") + " (" + getUnitGroup().getDefaultUnit().getUnit() + ")";
+       }
+       
+       @Override
+       public double computeValue(Simulation simulation) throws OptimizationException {
+               Coordinate cp, cg;
+               double cpx, cgx;
+               double stability;
+               
+               log.debug("Calculating stability of simulation, absolute=" + absolute);
+               
+               /*
+                * These are instantiated each time because this class must be thread-safe.
+                * Caching would in any case be inefficient since the rocket changes all the time.
+                */
+               AerodynamicCalculator aerodynamicCalculator = new BarrowmanCalculator();
+               MassCalculator massCalculator = new BasicMassCalculator();
+               
+
+               Configuration configuration = simulation.getConfiguration();
+               FlightConditions conditions = new FlightConditions(configuration);
+               conditions.setMach(Prefs.getDefaultMach());
+               conditions.setAOA(0);
+               conditions.setRollRate(0);
+               
+               cp = aerodynamicCalculator.getWorstCP(configuration, conditions, null);
+               cg = massCalculator.getCG(configuration, MassCalcType.LAUNCH_MASS);
+               
+               if (cp.weight > 0.000001)
+                       cpx = cp.x;
+               else
+                       cpx = Double.NaN;
+               
+               if (cg.weight > 0.000001)
+                       cgx = cg.x;
+               else
+                       cgx = Double.NaN;
+               
+
+               // Calculate the reference (absolute or relative)
+               stability = cpx - cgx;
+               
+               if (!absolute) {
+                       double diameter = 0;
+                       for (RocketComponent c : configuration) {
+                               if (c instanceof SymmetricComponent) {
+                                       double d1 = ((SymmetricComponent) c).getForeRadius() * 2;
+                                       double d2 = ((SymmetricComponent) c).getAftRadius() * 2;
+                                       diameter = MathUtil.max(diameter, d1, d2);
+                               }
+                       }
+                       stability = stability / diameter;
+               }
+               
+               log.debug("Resulting stability is " + stability + ", absolute=" + absolute);
+               
+               return stability;
+       }
+       
+       @Override
+       public UnitGroup getUnitGroup() {
+               if (absolute) {
+                       return UnitGroup.UNITS_LENGTH;
+               } else {
+                       return UnitGroup.UNITS_STABILITY_CALIBERS;
+               }
+       }
+       
+}
index 00178d0e38d6de482cc3d6ef311b9d3aa103894d..65b9ab987066d5f50bbffaef03e3c5e430494da1 100644 (file)
@@ -6,7 +6,12 @@ import java.util.List;
 
 import net.sf.openrocket.document.OpenRocketDocument;
 import net.sf.openrocket.optimization.rocketoptimization.OptimizableParameter;
+import net.sf.openrocket.optimization.rocketoptimization.parameters.GroundHitVelocityParameter;
+import net.sf.openrocket.optimization.rocketoptimization.parameters.LandingDistanceParameter;
+import net.sf.openrocket.optimization.rocketoptimization.parameters.MaximumAccelerationParameter;
 import net.sf.openrocket.optimization.rocketoptimization.parameters.MaximumAltitudeParameter;
+import net.sf.openrocket.optimization.rocketoptimization.parameters.MaximumVelocityParameter;
+import net.sf.openrocket.optimization.rocketoptimization.parameters.StabilityParameter;
 
 /**
  * Default implementation for optimization parameter service.
@@ -20,6 +25,12 @@ public class DefaultOptimizableParameterService implements OptimizableParameterS
                List<OptimizableParameter> list = new ArrayList<OptimizableParameter>();
                
                list.add(new MaximumAltitudeParameter());
+               list.add(new MaximumVelocityParameter());
+               list.add(new MaximumAccelerationParameter());
+               list.add(new StabilityParameter(false));
+               list.add(new StabilityParameter(true));
+               list.add(new GroundHitVelocityParameter());
+               list.add(new LandingDistanceParameter());
                
                return list;
        }
index 977cd1866dc5d8242a86ad4b09231705bdfa8f99..565330aae1b25d7f36ee4a7408b6aca0e23b96d0 100644 (file)
@@ -7,21 +7,41 @@ import java.util.List;
 import java.util.Map;
 
 import net.sf.openrocket.document.OpenRocketDocument;
+import net.sf.openrocket.document.Simulation;
 import net.sf.openrocket.l10n.Translator;
+import net.sf.openrocket.optimization.general.OptimizationException;
 import net.sf.openrocket.optimization.rocketoptimization.SimulationModifier;
 import net.sf.openrocket.optimization.rocketoptimization.modifiers.GenericComponentModifier;
 import net.sf.openrocket.rocketcomponent.BodyTube;
+import net.sf.openrocket.rocketcomponent.EllipticalFinSet;
+import net.sf.openrocket.rocketcomponent.FinSet;
+import net.sf.openrocket.rocketcomponent.FreeformFinSet;
+import net.sf.openrocket.rocketcomponent.InternalComponent;
+import net.sf.openrocket.rocketcomponent.LaunchLug;
+import net.sf.openrocket.rocketcomponent.MassComponent;
+import net.sf.openrocket.rocketcomponent.MotorMount;
 import net.sf.openrocket.rocketcomponent.NoseCone;
+import net.sf.openrocket.rocketcomponent.Parachute;
+import net.sf.openrocket.rocketcomponent.RecoveryDevice;
+import net.sf.openrocket.rocketcomponent.RecoveryDevice.DeployEvent;
 import net.sf.openrocket.rocketcomponent.Rocket;
 import net.sf.openrocket.rocketcomponent.RocketComponent;
+import net.sf.openrocket.rocketcomponent.Streamer;
 import net.sf.openrocket.rocketcomponent.Transition;
+import net.sf.openrocket.rocketcomponent.TrapezoidFinSet;
 import net.sf.openrocket.startup.Application;
 import net.sf.openrocket.unit.UnitGroup;
+import net.sf.openrocket.util.BugException;
+import net.sf.openrocket.util.Reflection;
+import net.sf.openrocket.util.Reflection.Method;
 
 public class DefaultSimulationModifierService implements SimulationModifierService {
        
        private static final Translator trans = Application.getTranslator();
        
+       private static final double DEFAULT_RANGE_MULTIPLIER = 2.0;
+       
+
        private static final Map<Class<?>, List<ModifierDefinition>> definitions = new HashMap<Class<?>, List<ModifierDefinition>>();
        static {
                //addModifier("optimization.modifier.", unitGroup, multiplier, componentClass, methodName);
@@ -33,23 +53,56 @@ public class DefaultSimulationModifierService implements SimulationModifierServi
                 */
 
                addModifier("optimization.modifier.nosecone.length", UnitGroup.UNITS_LENGTH, 1.0, NoseCone.class, "Length");
-               addModifier("optimization.modifier.nosecone.diameter", UnitGroup.UNITS_LENGTH, 2.0, NoseCone.class, "AftRadius");
-               addModifier("optimization.modifier.nosecone.thickness", UnitGroup.UNITS_LENGTH, 1.0, NoseCone.class, "Thickness");
+               addModifier("optimization.modifier.nosecone.diameter", UnitGroup.UNITS_LENGTH, 2.0, NoseCone.class, "AftRadius", "isAftRadiusAutomatic");
+               addModifier("optimization.modifier.nosecone.thickness", UnitGroup.UNITS_LENGTH, 1.0, NoseCone.class, "Thickness", "isFilled");
                
                addModifier("optimization.modifier.transition.length", UnitGroup.UNITS_LENGTH, 1.0, Transition.class, "Length");
-               addModifier("optimization.modifier.transition.forediameter", UnitGroup.UNITS_LENGTH, 2.0, Transition.class, "ForeRadius");
-               addModifier("optimization.modifier.transition.aftdiameter", UnitGroup.UNITS_LENGTH, 2.0, Transition.class, "AftRadius");
-               addModifier("optimization.modifier.transition.thickness", UnitGroup.UNITS_LENGTH, 1.0, Transition.class, "Thickness");
+               addModifier("optimization.modifier.transition.forediameter", UnitGroup.UNITS_LENGTH, 2.0, Transition.class, "ForeRadius", "isForeRadiusAutomatic");
+               addModifier("optimization.modifier.transition.aftdiameter", UnitGroup.UNITS_LENGTH, 2.0, Transition.class, "AftRadius", "isAftRadiusAutomatic");
+               addModifier("optimization.modifier.transition.thickness", UnitGroup.UNITS_LENGTH, 1.0, Transition.class, "Thickness", "isFilled");
                
                addModifier("optimization.modifier.bodytube.length", UnitGroup.UNITS_LENGTH, 1.0, BodyTube.class, "Length");
-               addModifier("optimization.modifier.bodytube.outerDiameter", UnitGroup.UNITS_LENGTH, 2.0, BodyTube.class, "OuterRadius");
-               addModifier("optimization.modifier.bodytube.thickness", UnitGroup.UNITS_LENGTH, 1.0, BodyTube.class, "Thickness");
+               addModifier("optimization.modifier.bodytube.outerDiameter", UnitGroup.UNITS_LENGTH, 2.0, BodyTube.class, "OuterRadius", "isOuterRadiusAutomatic");
+               addModifier("optimization.modifier.bodytube.thickness", UnitGroup.UNITS_LENGTH, 1.0, BodyTube.class, "Thickness", "isFilled");
+               
+               addModifier("optimization.modifier.trapezoidfinset.rootChord", UnitGroup.UNITS_LENGTH, 1.0, TrapezoidFinSet.class, "RootChord");
+               addModifier("optimization.modifier.trapezoidfinset.tipChord", UnitGroup.UNITS_LENGTH, 1.0, TrapezoidFinSet.class, "TipChord");
+               addModifier("optimization.modifier.trapezoidfinset.sweep", UnitGroup.UNITS_LENGTH, 1.0, TrapezoidFinSet.class, "Sweep");
+               addModifier("optimization.modifier.trapezoidfinset.height", UnitGroup.UNITS_LENGTH, 1.0, TrapezoidFinSet.class, "Height");
+               addModifier("optimization.modifier.finset.cant", UnitGroup.UNITS_ANGLE, 1.0, TrapezoidFinSet.class, "CantAngle");
+               
+               addModifier("optimization.modifier.ellipticalfinset.length", UnitGroup.UNITS_LENGTH, 1.0, EllipticalFinSet.class, "Length");
+               addModifier("optimization.modifier.ellipticalfinset.height", UnitGroup.UNITS_LENGTH, 1.0, EllipticalFinSet.class, "Height");
+               addModifier("optimization.modifier.finset.cant", UnitGroup.UNITS_ANGLE, 1.0, EllipticalFinSet.class, "CantAngle");
+               
+               addModifier("optimization.modifier.finset.cant", UnitGroup.UNITS_ANGLE, 1.0, FreeformFinSet.class, "CantAngle");
+               
+               addModifier("optimization.modifier.launchlug.length", UnitGroup.UNITS_LENGTH, 1.0, LaunchLug.class, "Length");
+               addModifier("optimization.modifier.launchlug.outerDiameter", UnitGroup.UNITS_LENGTH, 2.0, LaunchLug.class, "OuterRadius");
+               addModifier("optimization.modifier.launchlug.thickness", UnitGroup.UNITS_LENGTH, 1.0, LaunchLug.class, "Thickness");
                
 
+               addModifier("optimization.modifier.masscomponent.mass", UnitGroup.UNITS_MASS, 1.0, MassComponent.class, "ComponentMass");
+               
+               addModifier("optimization.modifier.parachute.diameter", UnitGroup.UNITS_LENGTH, 1.0, Parachute.class, "Diameter");
+               addModifier("optimization.modifier.parachute.coefficient", UnitGroup.UNITS_NONE, 1.0, Parachute.class, "CD");
+               
+               addModifier("optimization.modifier.streamer.length", UnitGroup.UNITS_LENGTH, 1.0, Streamer.class, "StripLength");
+               addModifier("optimization.modifier.streamer.width", UnitGroup.UNITS_LENGTH, 1.0, Streamer.class, "StripWidth");
+               addModifier("optimization.modifier.streamer.aspectRatio", UnitGroup.UNITS_NONE, 1.0, Streamer.class, "AspectRatio");
+               addModifier("optimization.modifier.streamer.coefficient", UnitGroup.UNITS_NONE, 1.0, Streamer.class, "CD", "isCDAutomatic");
+               
+       }
+       
+       private static void addModifier(String modifierNameKey, UnitGroup unitGroup, double multiplier,
+                       Class<? extends RocketComponent> componentClass, String methodName) {
+               addModifier(modifierNameKey, unitGroup, multiplier, componentClass, methodName, null);
        }
        
        private static void addModifier(String modifierNameKey, UnitGroup unitGroup, double multiplier,
-                               Class<? extends RocketComponent> componentClass, String methodName) {
+                               Class<? extends RocketComponent> componentClass, String methodName, String autoMethod) {
+               
+               String modifierDescriptionKey = modifierNameKey + ".desc";
                
                List<ModifierDefinition> list = definitions.get(componentClass);
                if (list == null) {
@@ -57,7 +110,8 @@ public class DefaultSimulationModifierService implements SimulationModifierServi
                        definitions.put(componentClass, list);
                }
                
-               ModifierDefinition definition = new ModifierDefinition(modifierNameKey, unitGroup, multiplier, componentClass, methodName);
+               ModifierDefinition definition = new ModifierDefinition(modifierNameKey, modifierDescriptionKey, unitGroup,
+                               multiplier, componentClass, methodName, autoMethod);
                list.add(definition);
        }
        
@@ -69,29 +123,182 @@ public class DefaultSimulationModifierService implements SimulationModifierServi
                List<SimulationModifier> modifiers = new ArrayList<SimulationModifier>();
                
                Rocket rocket = document.getRocket();
+               
+               // Simulation is used to calculate default min/max values
+               Simulation simulation = new Simulation(rocket);
+               simulation.getConfiguration().setMotorConfigurationID(null);
+               
                for (RocketComponent c : rocket) {
                        
                        // Attribute modifiers
                        List<ModifierDefinition> list = definitions.get(c.getClass());
                        if (list != null) {
                                for (ModifierDefinition def : list) {
+                                       
+                                       // Ignore modifier if value is set to automatic
+                                       if (def.autoMethod != null) {
+                                               Method m = Reflection.findMethod(c.getClass(), def.autoMethod);
+                                               if ((Boolean) m.invoke(c)) {
+                                                       continue;
+                                               }
+                                       }
+                                       
                                        SimulationModifier mod = new GenericComponentModifier(
-                                                       trans.get(def.modifierNameKey), c, def.unitGroup, def.multiplier, def.componentClass,
-                                                       c.getID(), def.methodName);
+                                                       trans.get(def.modifierNameKey), trans.get(def.modifierDescriptionKey), c, def.unitGroup,
+                                                       def.multiplier, def.componentClass, c.getID(), def.methodName);
+                                       setDefaultMinMax(mod, simulation);
                                        modifiers.add(mod);
                                }
                        }
                        
-                       // TODO: HIGH: Conditional modifiers (overrides)
+
+                       // Add override modifiers if mass/CG is overridden
+                       if (c.isMassOverridden()) {
+                               SimulationModifier mod = new GenericComponentModifier(
+                                               trans.get("optimization.modifier.rocketcomponent.overrideMass"),
+                                               trans.get("optimization.modifier.rocketcomponent.overrideMass.desc"),
+                                               c, UnitGroup.UNITS_MASS,
+                                               1.0, c.getClass(), c.getID(), "OverrideMass");
+                               setDefaultMinMax(mod, simulation);
+                               modifiers.add(mod);
+                       }
+                       if (c.isCGOverridden()) {
+                               SimulationModifier mod = new GenericComponentModifier(
+                                               trans.get("optimization.modifier.rocketcomponent.overrideCG"),
+                                               trans.get("optimization.modifier.rocketcomponent.overrideCG.desc"),
+                                               c, UnitGroup.UNITS_LENGTH,
+                                               1.0, c.getClass(), c.getID(), "OverrideCGX");
+                               mod.setMinValue(0);
+                               mod.setMaxValue(c.getLength());
+                               modifiers.add(mod);
+                       }
+                       
+
+                       // Conditional motor mount parameters
+                       if (c instanceof MotorMount) {
+                               MotorMount mount = (MotorMount) c;
+                               if (mount.isMotorMount()) {
+                                       
+                                       SimulationModifier mod = new GenericComponentModifier(
+                                                       trans.get("optimization.modifier.motormount.overhang"),
+                                                       trans.get("optimization.modifier.motormount.overhang.desc"),
+                                                       c, UnitGroup.UNITS_LENGTH,
+                                                       1.0, c.getClass(), c.getID(), "MotorOverhang");
+                                       setDefaultMinMax(mod, simulation);
+                                       modifiers.add(mod);
+                                       
+                                       mod = new GenericComponentModifier(
+                                                       trans.get("optimization.modifier.motormount.delay"),
+                                                       trans.get("optimization.modifier.motormount.delay.desc"),
+                                                       c, UnitGroup.UNITS_SHORT_TIME,
+                                                       1.0, c.getClass(), c.getID(), "IgnitionDelay");
+                                       mod.setMinValue(0);
+                                       mod.setMaxValue(5);
+                                       modifiers.add(mod);
+                                       
+                               }
+                       }
                        
-                       // TODO: Transition / Nose cone shape parameter (conditional)
+
+                       // Inner component positioning
+                       if (c instanceof InternalComponent) {
+                               RocketComponent parent = c.getParent();
+                               SimulationModifier mod = new GenericComponentModifier(
+                                               trans.get("optimization.modifier.internalcomponent.position"),
+                                               trans.get("optimization.modifier.internalcomponent.position.desc"),
+                                               c, UnitGroup.UNITS_LENGTH,
+                                               1.0, c.getClass(), c.getID(), "PositionValue");
+                               mod.setMinValue(0);
+                               mod.setMaxValue(parent.getLength());
+                               modifiers.add(mod);
+                       }
+                       
+
+                       // Custom min/max for fin set position
+                       if (c instanceof FinSet) {
+                               RocketComponent parent = c.getParent();
+                               SimulationModifier mod = new GenericComponentModifier(
+                                               trans.get("optimization.modifier.finset.position"),
+                                               trans.get("optimization.modifier.finset.position.desc"),
+                                               c, UnitGroup.UNITS_LENGTH,
+                                               1.0, c.getClass(), c.getID(), "PositionValue");
+                               mod.setMinValue(0);
+                               mod.setMaxValue(parent.getLength());
+                               modifiers.add(mod);
+                       }
+                       
+
+                       // Custom min/max for launch lug position
+                       if (c instanceof LaunchLug) {
+                               RocketComponent parent = c.getParent();
+                               SimulationModifier mod = new GenericComponentModifier(
+                                               trans.get("optimization.modifier.launchlug.position"),
+                                               trans.get("optimization.modifier.launchlug.position.desc"),
+                                               c, UnitGroup.UNITS_LENGTH,
+                                               1.0, c.getClass(), c.getID(), "PositionValue");
+                               mod.setMinValue(0);
+                               mod.setMaxValue(parent.getLength());
+                               modifiers.add(mod);
+                       }
+                       
+
+                       // Recovery device deployment altitude and delay
+                       if (c instanceof RecoveryDevice) {
+                               RecoveryDevice device = (RecoveryDevice) c;
+                               
+                               SimulationModifier mod = new GenericComponentModifier(
+                                               trans.get("optimization.modifier.recoverydevice.deployDelay"),
+                                               trans.get("optimization.modifier.recoverydevice.deployDelay.desc"),
+                                               c, UnitGroup.UNITS_SHORT_TIME,
+                                               1.0, c.getClass(), c.getID(), "DeployDelay");
+                               mod.setMinValue(0);
+                               mod.setMaxValue(10);
+                               modifiers.add(mod);
+                               
+                               if (device.getDeployEvent() == DeployEvent.ALTITUDE) {
+                                       mod = new GenericComponentModifier(
+                                                       trans.get("optimization.modifier.recoverydevice.deployAltitude"),
+                                                       trans.get("optimization.modifier.recoverydevice.deployAltitude.desc"),
+                                                       c, UnitGroup.UNITS_DISTANCE,
+                                                       1.0, c.getClass(), c.getID(), "DeployAltitude");
+                                       setDefaultMinMax(mod, simulation);
+                                       modifiers.add(mod);
+                               }
+                       }
+                       
+
+                       // Conditional shape parameter of Transition
+                       if (c instanceof Transition) {
+                               Transition transition = (Transition) c;
+                               Transition.Shape shape = transition.getType();
+                               if (shape.usesParameter()) {
+                                       SimulationModifier mod = new GenericComponentModifier(
+                                                       trans.get("optimization.modifier." + c.getClass().getSimpleName().toLowerCase() + ".shapeparameter"),
+                                                       trans.get("optimization.modifier." + c.getClass().getSimpleName().toLowerCase() + ".shapeparameter.desc"),
+                                                       c, UnitGroup.UNITS_NONE,
+                                                       1.0, c.getClass(), c.getID(), "ShapeParameter");
+                                       mod.setMinValue(shape.minParameter());
+                                       mod.setMaxValue(shape.maxParameter());
+                                       modifiers.add(mod);
+                               }
+                       }
                }
                
                return modifiers;
        }
        
        
-
+       private void setDefaultMinMax(SimulationModifier mod, Simulation simulation) {
+               try {
+                       double current = mod.getCurrentSIValue(simulation);
+                       mod.setMinValue(current / DEFAULT_RANGE_MULTIPLIER);
+                       mod.setMaxValue(current * DEFAULT_RANGE_MULTIPLIER);
+               } catch (OptimizationException e) {
+                       throw new BugException("Simulation modifier threw exception", e);
+               }
+       }
+       
+       
        /*
         * String modifierName, Object relatedObject, UnitGroup unitGroup,
                        double multiplier, Class<? extends RocketComponent> componentClass, String componentId, String methodName
@@ -99,20 +306,23 @@ public class DefaultSimulationModifierService implements SimulationModifierServi
 
        private static class ModifierDefinition {
                private final String modifierNameKey;
+               private final String modifierDescriptionKey;
                private final UnitGroup unitGroup;
                private final double multiplier;
                private final Class<? extends RocketComponent> componentClass;
                private final String methodName;
+               private final String autoMethod;
                
                
-               public ModifierDefinition(String modifierNameKey, UnitGroup unitGroup, double multiplier,
-                               Class<? extends RocketComponent> componentClass, String methodName) {
-                       super();
+               public ModifierDefinition(String modifierNameKey, String modifierDescriptionKey, UnitGroup unitGroup,
+                               double multiplier, Class<? extends RocketComponent> componentClass, String methodName, String autoMethod) {
                        this.modifierNameKey = modifierNameKey;
+                       this.modifierDescriptionKey = modifierDescriptionKey;
                        this.unitGroup = unitGroup;
                        this.multiplier = multiplier;
                        this.componentClass = componentClass;
                        this.methodName = methodName;
+                       this.autoMethod = autoMethod;
                }
                
        }
index 9ab9c0b3c8f5dbf62cdf591e3f10d35d5aba92b7..be00f1d49607c0c14857ddea83dbb0df3c8e9c0d 100644 (file)
@@ -328,7 +328,7 @@ public class Configuration implements Cloneable, ChangeSource, ComponentChangeLi
        
        /**
         * Perform a deep-clone.  The object references are also cloned and no
-        * listeners are listening on the cloned object.  
+        * listeners are listening on the cloned object.  The rocket instance remains the same.
         */
        @Override
        public Configuration clone() {
index d628478a763bba342620e2b9d133ad18d6bd7b23..5e40810b4749fc1268648d7d9da484a049c7e3b1 100644 (file)
@@ -513,7 +513,7 @@ public abstract class FinSet extends ExternalComponent {
                
                double radius = getBodyRadius();
                
-               return fins * (inertia + MathUtil.pow2(Math.sqrt(h2) + radius));
+               return fins * (inertia + MathUtil.pow2(MathUtil.safeSqrt(h2) + radius));
        }
        
        
@@ -538,9 +538,9 @@ public abstract class FinSet extends ExternalComponent {
                double h = getSpan();
                
                if (MathUtil.equals(w * h, 0)) {
-                       h = Math.sqrt(area);
+                       h = MathUtil.safeSqrt(area);
                } else {
-                       h = Math.sqrt(h * area / w);
+                       h = MathUtil.safeSqrt(h * area / w);
                }
                
                if (fins == 1)
index 47911aa069fcba04ec1ef69d8305eade4dc8c55b..49c139b0bbf1a06416e0c1bf36463f2b4cac45e1 100644 (file)
@@ -4,9 +4,15 @@ import net.sf.openrocket.l10n.Translator;
 import net.sf.openrocket.startup.Application;
 import net.sf.openrocket.util.MathUtil;
 
+/**
+ * This class represents a generic component that has a specific mass and an approximate shape.
+ * The mass is accessed via get/setComponentMass.
+ * 
+ * @author Sampo Niskanen <sampo.niskanen@iki.fi>
+ */
 public class MassComponent extends MassObject {
        private static final Translator trans = Application.getTranslator();
-
+       
        private double mass = 0;
        
        
index 0ce768f9168b02e9e18b79d8c028965206824d1f..4403d25558aa2d6a4b420cae18fcecb8ea80a603 100644 (file)
@@ -8,7 +8,7 @@ import net.sf.openrocket.util.Prefs;
 
 public class Parachute extends RecoveryDevice {
        private static final Translator trans = Application.getTranslator();
-
+       
        public static final double DEFAULT_CD = 0.8;
        
        private double diameter;
@@ -94,7 +94,7 @@ public class Parachute extends RecoveryDevice {
        public void setArea(double area) {
                if (MathUtil.equals(getArea(), area))
                        return;
-               diameter = Math.sqrt(area / Math.PI) * 2;
+               diameter = MathUtil.safeSqrt(area / Math.PI) * 2;
                fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE);
        }
        
index dd071a40716481ef9d3abbd1806a27da1fb9c2ff..8d73c45718b009a44c80aa5314c0314a19de3833 100644 (file)
@@ -60,7 +60,7 @@ public class Streamer extends RecoveryDevice {
                
                ratio = Math.max(ratio, 0.01);
                double area = getArea();
-               stripWidth = Math.sqrt(area / ratio);
+               stripWidth = MathUtil.safeSqrt(area / ratio);
                stripLength = ratio * stripWidth;
                fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE);
        }
@@ -76,7 +76,7 @@ public class Streamer extends RecoveryDevice {
                        return;
                
                double ratio = Math.max(getAspectRatio(), 0.01);
-               stripWidth = Math.sqrt(area / ratio);
+               stripWidth = MathUtil.safeSqrt(area / ratio);
                stripLength = ratio * stripWidth;
                fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE);
        }
index ac8318def028abff4ad1a01b443f83c47e9fb76e..275de2051d5320b1466d36fc6a74b4f96504b256 100644 (file)
@@ -21,12 +21,12 @@ public abstract class SymmetricComponent extends BodyComponent implements Radial
        public static final double DEFAULT_RADIUS = 0.025;
        public static final double DEFAULT_THICKNESS = 0.002;
        
-       private static final int DIVISIONS = 100;  // No. of divisions when integrating
+       private static final int DIVISIONS = 100; // No. of divisions when integrating
        
        protected boolean filled = false;
        protected double thickness = DEFAULT_THICKNESS;
        
-       
+
        // Cached data, default values signify not calculated
        private double wetArea = -1;
        private double planArea = -1;
@@ -38,11 +38,11 @@ public abstract class SymmetricComponent extends BodyComponent implements Radial
        private Coordinate cg = null;
        
        
-       
+
        public SymmetricComponent() {
                super();
        }
-
+       
        
        /**
         * Return the component radius at position x.
@@ -51,15 +51,21 @@ public abstract class SymmetricComponent extends BodyComponent implements Radial
         *          the component.
         */
        public abstract double getRadius(double x);
+       
+       @Override
        public abstract double getInnerRadius(double x);
-
+       
        public abstract double getForeRadius();
+       
        public abstract boolean isForeRadiusAutomatic();
+       
        public abstract double getAftRadius();
+       
        public abstract boolean isAftRadiusAutomatic();
        
        
        // Implement the Radial interface:
+       @Override
        public final double getOuterRadius(double x) {
                return getRadius(x);
        }
@@ -76,14 +82,14 @@ public abstract class SymmetricComponent extends BodyComponent implements Radial
        }
        
        
-       
+
        /**
         * Return the component wall thickness.
         */
        public double getThickness() {
                if (filled)
-                       return Math.max(getForeRadius(),getAftRadius());
-               return Math.min(thickness,Math.max(getForeRadius(),getAftRadius()));
+                       return Math.max(getForeRadius(), getAftRadius());
+               return Math.min(thickness, Math.max(getForeRadius(), getAftRadius()));
        }
        
        
@@ -94,11 +100,11 @@ public abstract class SymmetricComponent extends BodyComponent implements Radial
        public void setThickness(double thickness) {
                if ((this.thickness == thickness) && !filled)
                        return;
-               this.thickness = MathUtil.clamp(thickness,0,Math.max(getForeRadius(),getAftRadius()));
+               this.thickness = MathUtil.clamp(thickness, 0, Math.max(getForeRadius(), getAftRadius()));
                filled = false;
                fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE);
        }
-
+       
        
        /**
         * Returns whether the component is set as filled.  If it is set filled, then the
@@ -120,23 +126,23 @@ public abstract class SymmetricComponent extends BodyComponent implements Radial
                fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE);
        }
        
-
+       
        /**
         * Adds component bounds at a number of points between 0...length.
         */
        @Override
        public Collection<Coordinate> getComponentBounds() {
                List<Coordinate> list = new ArrayList<Coordinate>(20);
-               for (int n=0; n<=5; n++) {
-                       double x = n*length/5;
+               for (int n = 0; n <= 5; n++) {
+                       double x = n * length / 5;
                        double r = getRadius(x);
-                       addBound(list,x,r);
+                       addBound(list, x, r);
                }
                return list;
        }
        
        
-       
+
        /**
         * Calculate volume of the component by integrating over the length of the component.
         * The method caches the result, so subsequent calls are instant.  Subclasses may
@@ -231,7 +237,7 @@ public abstract class SymmetricComponent extends BodyComponent implements Radial
        @Override
        public double getLongitudinalUnitInertia() {
                if (longitudinalInertia < 0) {
-                       if (getComponentVolume() > 0.0000001)  // == 0.1cm^3
+                       if (getComponentVolume() > 0.0000001) // == 0.1cm^3
                                integrateInertiaVolume();
                        else
                                integrateInertiaSurface();
@@ -257,7 +263,7 @@ public abstract class SymmetricComponent extends BodyComponent implements Radial
         * Performs integration over the length of the component and updates the cached variables.
         */
        private void integrate() {
-               double x,r1,r2;
+               double x, r1, r2;
                double cgx;
                
                // Check length > 0
@@ -270,12 +276,12 @@ public abstract class SymmetricComponent extends BodyComponent implements Radial
                        return;
                }
                
-               
+
                // Integrate for volume, CG, wetted area and planform area
                
-               final double l = length/DIVISIONS;
-               final double pil  = Math.PI*l;    // PI * l
-               final double pil3 = Math.PI*l/3;  // PI * l/3
+               final double l = length / DIVISIONS;
+               final double pil = Math.PI * l; // PI * l
+               final double pil3 = Math.PI * l / 3; // PI * l/3
                r1 = getRadius(0);
                x = 0;
                wetArea = 0;
@@ -285,44 +291,44 @@ public abstract class SymmetricComponent extends BodyComponent implements Radial
                volume = 0;
                cgx = 0;
                
-               for (int n=1; n<=DIVISIONS; n++) {
+               for (int n = 1; n <= DIVISIONS; n++) {
                        /*
                         * r1 and r2 are the two radii
                         * x is the position of r1
                         * hyp is the length of the hypotenuse from r1 to r2
                         * height if the y-axis height of the component if not filled
                         */
+
+                       r2 = getRadius(x + l);
+                       final double hyp = MathUtil.hypot(r2 - r1, l);
                        
-                       r2 = getRadius(x+l);
-                       final double hyp = MathUtil.hypot(r2-r1, l);
-                       
-                       
+
                        // Volume differential elements
                        final double dV;
                        final double dFullV;
                        
-                       dFullV = pil3*(r1*r1 + r1*r2 + r2*r2);
-                       if (filled || r1<thickness || r2<thickness) {
+                       dFullV = pil3 * (r1 * r1 + r1 * r2 + r2 * r2);
+                       if (filled || r1 < thickness || r2 < thickness) {
                                // Filled piece
                                dV = dFullV;
                        } else {
                                // Hollow piece
-                               final double height = thickness*hyp/l;
-                               dV = MathUtil.max(pil*height*(r1+r2-height), 0);
+                               final double height = thickness * hyp / l;
+                               dV = MathUtil.max(pil * height * (r1 + r2 - height), 0);
                        }
-
+                       
                        // Add to the volume-related components
                        volume += dV;
                        fullVolume += dFullV;
-                       cgx += (x+l/2)*dV;
+                       cgx += (x + l / 2) * dV;
                        
                        // Wetted area ( * PI at the end)
-                       wetArea += hyp*(r1+r2);
+                       wetArea += hyp * (r1 + r2);
                        
                        // Planform area & center
-                       final double p = l*(r1+r2);
+                       final double p = l * (r1 + r2);
                        planArea += p;
-                       planCenter += (x+l/2)*p;
+                       planCenter += (x + l / 2) * p;
                        
                        // Update for next iteration
                        r1 = r2;
@@ -334,14 +340,14 @@ public abstract class SymmetricComponent extends BodyComponent implements Radial
                if (planArea > 0)
                        planCenter /= planArea;
                
-               if (volume < 0.0000000001) {  // 0.1 mm^3
+               if (volume < 0.0000000001) { // 0.1 mm^3
                        volume = 0;
-                       cg = new Coordinate(length/2, 0, 0, 0);
+                       cg = new Coordinate(length / 2, 0, 0, 0);
                } else {
                        // getComponentMass is safe now
                        // Use super.getComponentMass() to ensure only the transition shape mass
                        // is used, not the shoulders
-                       cg = new Coordinate(cgx/volume,0,0,super.getComponentMass());
+                       cg = new Coordinate(cgx / volume, 0, 0, super.getComponentMass());
                }
        }
        
@@ -352,11 +358,11 @@ public abstract class SymmetricComponent extends BodyComponent implements Radial
         */
        private void integrateInertiaVolume() {
                double x, r1, r2;
-
-               final double l = length/DIVISIONS;
-               final double pil  = Math.PI*l;    // PI * l
-               final double pil3 = Math.PI*l/3;  // PI * l/3
-
+               
+               final double l = length / DIVISIONS;
+               final double pil = Math.PI * l; // PI * l
+               final double pil3 = Math.PI * l / 3; // PI * l/3
+               
                r1 = getRadius(0);
                x = 0;
                longitudinalInertia = 0;
@@ -364,34 +370,34 @@ public abstract class SymmetricComponent extends BodyComponent implements Radial
                
                double volume = 0;
                
-               for (int n=1; n<=DIVISIONS; n++) {
+               for (int n = 1; n <= DIVISIONS; n++) {
                        /*
                         * r1 and r2 are the two radii, outer is their average
                         * x is the position of r1
                         * hyp is the length of the hypotenuse from r1 to r2
                         * height if the y-axis height of the component if not filled
                         */
-                       r2 = getRadius(x+l);
-                       final double outer = (r1 + r2)/2;
-                       
+                       r2 = getRadius(x + l);
+                       final double outer = (r1 + r2) / 2;
                        
+
                        // Volume differential elements
                        final double inner;
                        final double dV;
                        
-                       if (filled || r1<thickness || r2<thickness) {
+                       if (filled || r1 < thickness || r2 < thickness) {
                                inner = 0;
-                               dV = pil3*(r1*r1 + r1*r2 + r2*r2);
+                               dV = pil3 * (r1 * r1 + r1 * r2 + r2 * r2);
                        } else {
-                               final double hyp = MathUtil.hypot(r2-r1, l);
-                               final double height = thickness*hyp/l;
-                               dV = pil*height*(r1+r2-height);
-                               inner = Math.max(outer-height, 0);
+                               final double hyp = MathUtil.hypot(r2 - r1, l);
+                               final double height = thickness * hyp / l;
+                               dV = pil * height * (r1 + r2 - height);
+                               inner = Math.max(outer - height, 0);
                        }
                        
-                       rotationalInertia += dV * (pow2(outer) + pow2(inner))/2;
-                       longitudinalInertia += dV * ((3 * (pow2(outer) + pow2(inner)) + pow2(l))/12 
-                                       + pow2(x+l/2));
+                       rotationalInertia += dV * (pow2(outer) + pow2(inner)) / 2;
+                       longitudinalInertia += dV * ((3 * (pow2(outer) + pow2(inner)) + pow2(l)) / 12
+                                       + pow2(x + l / 2));
                        
                        volume += dV;
                        
@@ -400,14 +406,14 @@ public abstract class SymmetricComponent extends BodyComponent implements Radial
                        x += l;
                }
                
-               if (MathUtil.equals(volume,0)) {
+               if (MathUtil.equals(volume, 0)) {
                        integrateInertiaSurface();
                        return;
                }
                
                rotationalInertia /= volume;
                longitudinalInertia /= volume;
-
+               
                // Shift longitudinal inertia to CG
                longitudinalInertia = Math.max(longitudinalInertia - pow2(getComponentCG().x), 0);
        }
@@ -420,31 +426,33 @@ public abstract class SymmetricComponent extends BodyComponent implements Radial
         */
        private void integrateInertiaSurface() {
                double x, r1, r2;
-
-               final double l = length/DIVISIONS;
-
+               
+               final double l = length / DIVISIONS;
+               
                r1 = getRadius(0);
+               System.out.println(r1);
                x = 0;
+               
                longitudinalInertia = 0;
                rotationalInertia = 0;
                
                double surface = 0;
                
-               for (int n=1; n<=DIVISIONS; n++) {
+               for (int n = 1; n <= DIVISIONS; n++) {
                        /*
                         * r1 and r2 are the two radii, outer is their average
                         * x is the position of r1
                         * hyp is the length of the hypotenuse from r1 to r2
                         * height if the y-axis height of the component if not filled
                         */
-                       r2 = getRadius(x+l);
-                       final double hyp = MathUtil.hypot(r2-r1, l);
-                       final double outer = (r1 + r2)/2;
+                       r2 = getRadius(x + l);
+                       final double hyp = MathUtil.hypot(r2 - r1, l);
+                       final double outer = (r1 + r2) / 2;
+                       
+                       final double dS = hyp * (r1 + r2) * Math.PI;
                        
-                       final double dS = hyp * (r1+r2) * Math.PI;
-
                        rotationalInertia += dS * pow2(outer);
-                       longitudinalInertia += dS * ((6 * pow2(outer) + pow2(l))/12 + pow2(x+l/2));
+                       longitudinalInertia += dS * ((6 * pow2(outer) + pow2(l)) / 12 + pow2(x + l / 2));
                        
                        surface += dS;
                        
@@ -452,7 +460,7 @@ public abstract class SymmetricComponent extends BodyComponent implements Radial
                        r1 = r2;
                        x += l;
                }
-
+               
                if (MathUtil.equals(surface, 0)) {
                        longitudinalInertia = 0;
                        rotationalInertia = 0;
@@ -467,8 +475,8 @@ public abstract class SymmetricComponent extends BodyComponent implements Radial
        }
        
        
-       
-       
+
+
        /**
         * Invalidates the cached volume and CG information.
         */
@@ -488,10 +496,10 @@ public abstract class SymmetricComponent extends BodyComponent implements Radial
        }
        
        
-       
+
        ///////////   Auto radius helper methods
        
-       
+
        /**
         * Returns the automatic radius for this component towards the 
         * front of the rocket.  The automatics will not search towards the
@@ -500,7 +508,7 @@ public abstract class SymmetricComponent extends BodyComponent implements Radial
         * match was not found.
         */
        protected abstract double getFrontAutoRadius();
-
+       
        /**
         * Returns the automatic radius for this component towards the
         * end of the rocket.  The automatics will not search towards the
@@ -511,7 +519,7 @@ public abstract class SymmetricComponent extends BodyComponent implements Radial
        protected abstract double getRearAutoRadius();
        
        
-       
+
        /**
         * Return the previous symmetric component, or null if none exists.
         * NOTE: This method currently assumes that there are no external
@@ -523,11 +531,11 @@ public abstract class SymmetricComponent extends BodyComponent implements Radial
                RocketComponent c;
                for (c = this.getPreviousComponent(); c != null; c = c.getPreviousComponent()) {
                        if (c instanceof SymmetricComponent) {
-                               return (SymmetricComponent)c;
+                               return (SymmetricComponent) c;
                        }
                        if (!(c instanceof Stage) &&
-                                (c.relativePosition == RocketComponent.Position.AFTER))
-                               return null;   // Bad component type as "parent"
+                                       (c.relativePosition == RocketComponent.Position.AFTER))
+                               return null; // Bad component type as "parent"
                }
                return null;
        }
@@ -543,11 +551,11 @@ public abstract class SymmetricComponent extends BodyComponent implements Radial
                RocketComponent c;
                for (c = this.getNextComponent(); c != null; c = c.getNextComponent()) {
                        if (c instanceof SymmetricComponent) {
-                               return (SymmetricComponent)c;
+                               return (SymmetricComponent) c;
                        }
                        if (!(c instanceof Stage) &&
-                                (c.relativePosition == RocketComponent.Position.AFTER))
-                               return null;   // Bad component type as "parent"
+                                       (c.relativePosition == RocketComponent.Position.AFTER))
+                               return null; // Bad component type as "parent"
                }
                return null;
        }
index 1c2adb36cbdb1ccb8e9a7aa38d8ce123dd3bf294..f3a6f9b6a8764925dcb53939fc0244922774da28 100644 (file)
@@ -1,6 +1,6 @@
 package net.sf.openrocket.rocketcomponent;
 
-import static java.lang.Math.*;
+import static java.lang.Math.sin;
 import static net.sf.openrocket.util.MathUtil.*;
 
 import java.util.Collection;
@@ -581,12 +581,12 @@ public class Transition extends SymmetricComponent {
                                        return CONICAL.getRadius(x, radius, length, param);
                                
                                // Radius of circle is:
-                               double R = sqrt((pow2(length) + pow2(radius)) *
+                               double R = MathUtil.safeSqrt((pow2(length) + pow2(radius)) *
                                                (pow2((2 - param) * length) + pow2(param * radius)) / (4 * pow2(param * radius)));
                                double L = length / param;
                                //                              double R = (radius + length*length/(radius*param*param))/2;
-                               double y0 = sqrt(R * R - L * L);
-                               return sqrt(R * R - (L - x) * (L - x)) - y0;
+                               double y0 = MathUtil.safeSqrt(R * R - L * L);
+                               return MathUtil.safeSqrt(R * R - (L - x) * (L - x)) - y0;
                        }
                },
                
@@ -605,7 +605,7 @@ public class Transition extends SymmetricComponent {
                                assert x <= length;
                                assert radius >= 0;
                                x = x * radius / length;
-                               return sqrt(2 * radius * x - x * x); // radius/length * sphere
+                               return MathUtil.safeSqrt(2 * radius * x - x * x); // radius/length * sphere
                        }
                },
                
@@ -699,10 +699,10 @@ public class Transition extends SymmetricComponent {
                                assert param <= 2;
                                
                                double theta = Math.acos(1 - 2 * x / length);
-                               if (param == 0) {
-                                       return radius * sqrt((theta - sin(2 * theta) / 2) / Math.PI);
+                               if (MathUtil.equals(param, 0)) {
+                                       return radius * MathUtil.safeSqrt((theta - sin(2 * theta) / 2) / Math.PI);
                                }
-                               return radius * sqrt((theta - sin(2 * theta) / 2 + param * pow3(sin(theta))) / Math.PI);
+                               return radius * MathUtil.safeSqrt((theta - sin(2 * theta) / 2 + param * pow3(sin(theta))) / Math.PI);
                        }
                },
                
index 21ccb2665391a491ca4a9148335526dea3133392..7bc635c715d64c2cc5114bfc210717ebf36b0f8e 100644 (file)
@@ -16,6 +16,7 @@ import net.sf.openrocket.rocketcomponent.LaunchLug;
 import net.sf.openrocket.rocketcomponent.MotorMount;
 import net.sf.openrocket.rocketcomponent.RecoveryDevice;
 import net.sf.openrocket.rocketcomponent.RocketComponent;
+import net.sf.openrocket.simulation.exception.MotorIgnitionException;
 import net.sf.openrocket.simulation.exception.SimulationException;
 import net.sf.openrocket.simulation.exception.SimulationLaunchException;
 import net.sf.openrocket.simulation.listeners.SimulationListenerHelper;
@@ -51,7 +52,7 @@ public class BasicEventSimulationEngine implements SimulationEngine {
                Configuration configuration = setupConfiguration(simulationConditions);
                MotorInstanceConfiguration motorConfiguration = setupMotorConfiguration(configuration);
                if (motorConfiguration.getMotorIDs().isEmpty()) {
-                       throw new SimulationLaunchException("No motors defined.");
+                       throw new MotorIgnitionException("No motors defined in the simulation.");
                }
                
                // Initialize the simulation
@@ -161,7 +162,9 @@ public class BasicEventSimulationEngine implements SimulationEngine {
                
                flightData.addBranch(status.getFlightData());
                
-               log.info("Warnings at the end of simulation:  " + flightData.getWarningSet());
+               if (!flightData.getWarningSet().isEmpty()) {
+                       log.info("Warnings at the end of simulation:  " + flightData.getWarningSet());
+               }
                
                // TODO: HIGH: Simulate branches
                return flightData;
@@ -494,7 +497,7 @@ public class BasicEventSimulationEngine implements SimulationEngine {
 
                // If no motor has ignited, abort
                if (!status.isMotorIgnited()) {
-                       throw new SimulationLaunchException("No motors ignited.");
+                       throw new MotorIgnitionException("No motors ignited.");
                }
                
                return ret;
diff --git a/src/net/sf/openrocket/simulation/GUISimulationConditions.java b/src/net/sf/openrocket/simulation/GUISimulationConditions.java
deleted file mode 100644 (file)
index d0b8391..0000000
+++ /dev/null
@@ -1,471 +0,0 @@
-package net.sf.openrocket.simulation;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Random;
-
-import javax.swing.event.ChangeEvent;
-import javax.swing.event.ChangeListener;
-
-import net.sf.openrocket.aerodynamics.BarrowmanCalculator;
-import net.sf.openrocket.masscalc.BasicMassCalculator;
-import net.sf.openrocket.models.atmosphere.AtmosphericModel;
-import net.sf.openrocket.models.atmosphere.ExtendedISAModel;
-import net.sf.openrocket.models.gravity.BasicGravityModel;
-import net.sf.openrocket.models.wind.PinkNoiseWindModel;
-import net.sf.openrocket.rocketcomponent.Rocket;
-import net.sf.openrocket.util.BugException;
-import net.sf.openrocket.util.ChangeSource;
-import net.sf.openrocket.util.MathUtil;
-
-// TODO: HIGH: Move somewhere else and clean up
-@Deprecated
-public class GUISimulationConditions implements ChangeSource, Cloneable {
-       
-       public static final double MAX_LAUNCH_ROD_ANGLE = Math.PI / 3;
-       
-       /**
-        * The ISA standard atmosphere.
-        */
-       private static final AtmosphericModel ISA_ATMOSPHERIC_MODEL = new ExtendedISAModel();
-       
-
-       private final Rocket rocket;
-       private String motorID = null;
-       
-
-       /*
-        * NOTE:  When adding/modifying parameters, they must also be added to the
-        * equals and copyFrom methods!!
-        */
-
-       // TODO: HIGH: Fetch default values from Prefs!
-       
-       private double launchRodLength = 1;
-       
-       /** Launch rod angle > 0, radians from vertical */
-       private double launchRodAngle = 0;
-       
-       /** Launch rod direction, 0 = upwind, PI = downwind. */
-       private double launchRodDirection = 0;
-       
-
-       private double windAverage = 2.0;
-       private double windTurbulence = 0.1;
-       
-       private double launchAltitude = 0;
-       private double launchLatitude = 45;
-       
-       private boolean useISA = true;
-       private double launchTemperature = ExtendedISAModel.STANDARD_TEMPERATURE;
-       private double launchPressure = ExtendedISAModel.STANDARD_PRESSURE;
-       private AtmosphericModel atmosphericModel = null;
-       
-
-       private double timeStep = RK4SimulationStepper.RECOMMENDED_TIME_STEP;
-       private double maximumAngle = RK4SimulationStepper.RECOMMENDED_ANGLE_STEP;
-       
-       private boolean calculateExtras = true;
-       
-
-       private List<ChangeListener> listeners = new ArrayList<ChangeListener>();
-       
-       
-
-       public GUISimulationConditions(Rocket rocket) {
-               this.rocket = rocket;
-       }
-       
-       
-
-       public Rocket getRocket() {
-               return rocket;
-       }
-       
-       
-       public String getMotorConfigurationID() {
-               return motorID;
-       }
-       
-       /**
-        * Set the motor configuration ID.  This must be a valid motor configuration ID of
-        * the rocket, otherwise the configuration is set to <code>null</code>.
-        * 
-        * @param id    the configuration to set.
-        */
-       public void setMotorConfigurationID(String id) {
-               if (id != null)
-                       id = id.intern();
-               if (!rocket.isMotorConfigurationID(id))
-                       id = null;
-               if (id == motorID)
-                       return;
-               motorID = id;
-               fireChangeEvent();
-       }
-       
-       
-       public double getLaunchRodLength() {
-               return launchRodLength;
-       }
-       
-       public void setLaunchRodLength(double launchRodLength) {
-               if (MathUtil.equals(this.launchRodLength, launchRodLength))
-                       return;
-               this.launchRodLength = launchRodLength;
-               fireChangeEvent();
-       }
-       
-       
-       public double getLaunchRodAngle() {
-               return launchRodAngle;
-       }
-       
-       public void setLaunchRodAngle(double launchRodAngle) {
-               launchRodAngle = MathUtil.clamp(launchRodAngle, 0, MAX_LAUNCH_ROD_ANGLE);
-               if (MathUtil.equals(this.launchRodAngle, launchRodAngle))
-                       return;
-               this.launchRodAngle = launchRodAngle;
-               fireChangeEvent();
-       }
-       
-       
-       public double getLaunchRodDirection() {
-               return launchRodDirection;
-       }
-       
-       public void setLaunchRodDirection(double launchRodDirection) {
-               launchRodDirection = MathUtil.reduce180(launchRodDirection);
-               if (MathUtil.equals(this.launchRodDirection, launchRodDirection))
-                       return;
-               this.launchRodDirection = launchRodDirection;
-               fireChangeEvent();
-       }
-       
-       
-
-       public double getWindSpeedAverage() {
-               return windAverage;
-       }
-       
-       public void setWindSpeedAverage(double windAverage) {
-               if (MathUtil.equals(this.windAverage, windAverage))
-                       return;
-               this.windAverage = MathUtil.max(windAverage, 0);
-               fireChangeEvent();
-       }
-       
-       
-       public double getWindSpeedDeviation() {
-               return windAverage * windTurbulence;
-       }
-       
-       public void setWindSpeedDeviation(double windDeviation) {
-               if (windAverage < 0.1) {
-                       windAverage = 0.1;
-               }
-               setWindTurbulenceIntensity(windDeviation / windAverage);
-       }
-       
-       
-       /**
-        * Return the wind turbulence intensity (standard deviation / average).
-        * 
-        * @return  the turbulence intensity
-        */
-       public double getWindTurbulenceIntensity() {
-               return windTurbulence;
-       }
-       
-       /**
-        * Set the wind standard deviation to match the given turbulence intensity.
-        * 
-        * @param intensity   the turbulence intensity
-        */
-       public void setWindTurbulenceIntensity(double intensity) {
-               // Does not check equality so that setWindSpeedDeviation can be sure of event firing
-               this.windTurbulence = intensity;
-               fireChangeEvent();
-       }
-       
-       
-
-
-
-       public double getLaunchAltitude() {
-               return launchAltitude;
-       }
-       
-       public void setLaunchAltitude(double altitude) {
-               if (MathUtil.equals(this.launchAltitude, altitude))
-                       return;
-               this.launchAltitude = altitude;
-               fireChangeEvent();
-       }
-       
-       
-       public double getLaunchLatitude() {
-               return launchLatitude;
-       }
-       
-       public void setLaunchLatitude(double launchLatitude) {
-               launchLatitude = MathUtil.clamp(launchLatitude, -90, 90);
-               if (MathUtil.equals(this.launchLatitude, launchLatitude))
-                       return;
-               this.launchLatitude = launchLatitude;
-               fireChangeEvent();
-       }
-       
-       
-
-
-
-       public boolean isISAAtmosphere() {
-               return useISA;
-       }
-       
-       public void setISAAtmosphere(boolean isa) {
-               if (isa == useISA)
-                       return;
-               useISA = isa;
-               fireChangeEvent();
-       }
-       
-       
-       public double getLaunchTemperature() {
-               return launchTemperature;
-       }
-       
-       
-
-       public void setLaunchTemperature(double launchTemperature) {
-               if (MathUtil.equals(this.launchTemperature, launchTemperature))
-                       return;
-               this.launchTemperature = launchTemperature;
-               this.atmosphericModel = null;
-               fireChangeEvent();
-       }
-       
-       
-
-       public double getLaunchPressure() {
-               return launchPressure;
-       }
-       
-       
-
-       public void setLaunchPressure(double launchPressure) {
-               if (MathUtil.equals(this.launchPressure, launchPressure))
-                       return;
-               this.launchPressure = launchPressure;
-               this.atmosphericModel = null;
-               fireChangeEvent();
-       }
-       
-       
-       /**
-        * Returns an atmospheric model corresponding to the launch conditions.  The
-        * atmospheric models may be shared between different calls.
-        * 
-        * @return      an AtmosphericModel object.
-        */
-       public AtmosphericModel getAtmosphericModel() {
-               if (useISA) {
-                       return ISA_ATMOSPHERIC_MODEL;
-               }
-               if (atmosphericModel == null) {
-                       atmosphericModel = new ExtendedISAModel(launchAltitude,
-                                       launchTemperature, launchPressure);
-               }
-               return atmosphericModel;
-       }
-       
-       
-       public double getTimeStep() {
-               return timeStep;
-       }
-       
-       public void setTimeStep(double timeStep) {
-               if (MathUtil.equals(this.timeStep, timeStep))
-                       return;
-               this.timeStep = timeStep;
-               fireChangeEvent();
-       }
-       
-       public double getMaximumStepAngle() {
-               return maximumAngle;
-       }
-       
-       public void setMaximumStepAngle(double maximumAngle) {
-               maximumAngle = MathUtil.clamp(maximumAngle, 1 * Math.PI / 180, 20 * Math.PI / 180);
-               if (MathUtil.equals(this.maximumAngle, maximumAngle))
-                       return;
-               this.maximumAngle = maximumAngle;
-               fireChangeEvent();
-       }
-       
-       
-
-       public boolean getCalculateExtras() {
-               return calculateExtras;
-       }
-       
-       
-
-       public void setCalculateExtras(boolean calculateExtras) {
-               if (this.calculateExtras == calculateExtras)
-                       return;
-               this.calculateExtras = calculateExtras;
-               fireChangeEvent();
-       }
-       
-       
-
-       @Override
-       public GUISimulationConditions clone() {
-               try {
-                       GUISimulationConditions copy = (GUISimulationConditions) super.clone();
-                       copy.listeners = new ArrayList<ChangeListener>();
-                       return copy;
-               } catch (CloneNotSupportedException e) {
-                       throw new BugException(e);
-               }
-       }
-       
-       
-       public void copyFrom(GUISimulationConditions src) {
-               
-               if (this.rocket == src.rocket) {
-                       
-                       this.motorID = src.motorID;
-                       
-               } else {
-                       
-                       if (src.rocket.hasMotors(src.motorID)) {
-                               // Try to find a matching motor ID
-                               String motorDesc = src.rocket.getMotorConfigurationDescription(src.motorID);
-                               String matchID = null;
-                               
-                               for (String id : this.rocket.getMotorConfigurationIDs()) {
-                                       if (motorDesc.equals(this.rocket.getMotorConfigurationDescription(id))) {
-                                               matchID = id;
-                                               break;
-                                       }
-                               }
-                               
-                               this.motorID = matchID;
-                       } else {
-                               this.motorID = null;
-                       }
-               }
-               
-               this.launchAltitude = src.launchAltitude;
-               this.launchLatitude = src.launchLatitude;
-               this.launchPressure = src.launchPressure;
-               this.launchRodAngle = src.launchRodAngle;
-               this.launchRodDirection = src.launchRodDirection;
-               this.launchRodLength = src.launchRodLength;
-               this.launchTemperature = src.launchTemperature;
-               this.maximumAngle = src.maximumAngle;
-               this.timeStep = src.timeStep;
-               this.windAverage = src.windAverage;
-               this.windTurbulence = src.windTurbulence;
-               this.calculateExtras = src.calculateExtras;
-               
-               fireChangeEvent();
-       }
-       
-       
-
-       /**
-        * Compares whether the two simulation conditions are equal.  The two are considered
-        * equal if the rocket, motor id and all variables are equal.
-        */
-       @Override
-       public boolean equals(Object other) {
-               if (!(other instanceof GUISimulationConditions))
-                       return false;
-               GUISimulationConditions o = (GUISimulationConditions) other;
-               return ((this.rocket == o.rocket) &&
-                               this.motorID == o.motorID &&
-                               MathUtil.equals(this.launchAltitude, o.launchAltitude) &&
-                               MathUtil.equals(this.launchLatitude, o.launchLatitude) &&
-                               MathUtil.equals(this.launchPressure, o.launchPressure) &&
-                               MathUtil.equals(this.launchRodAngle, o.launchRodAngle) &&
-                               MathUtil.equals(this.launchRodDirection, o.launchRodDirection) &&
-                               MathUtil.equals(this.launchRodLength, o.launchRodLength) &&
-                               MathUtil.equals(this.launchTemperature, o.launchTemperature) &&
-                               MathUtil.equals(this.maximumAngle, o.maximumAngle) &&
-                               MathUtil.equals(this.timeStep, o.timeStep) &&
-                               MathUtil.equals(this.windAverage, o.windAverage) &&
-                               MathUtil.equals(this.windTurbulence, o.windTurbulence) && this.calculateExtras == o.calculateExtras);
-       }
-       
-       /**
-        * Hashcode method compatible with {@link #equals(Object)}.
-        */
-       @Override
-       public int hashCode() {
-               if (motorID == null)
-                       return rocket.hashCode();
-               return rocket.hashCode() + motorID.hashCode();
-       }
-       
-       @Override
-       public void addChangeListener(ChangeListener listener) {
-               listeners.add(listener);
-       }
-       
-       @Override
-       public void removeChangeListener(ChangeListener listener) {
-               listeners.remove(listener);
-       }
-       
-       private final ChangeEvent event = new ChangeEvent(this);
-       
-       private void fireChangeEvent() {
-               ChangeListener[] array = listeners.toArray(new ChangeListener[0]);
-               
-               for (int i = array.length - 1; i >= 0; i--) {
-                       array[i].stateChanged(event);
-               }
-       }
-       
-       
-       // TODO: HIGH: Clean up
-       @Deprecated
-       public SimulationConditions toSimulationConditions() {
-               SimulationConditions conditions = new SimulationConditions();
-               
-               conditions.setRocket((Rocket) getRocket().copy());
-               conditions.setMotorConfigurationID(getMotorConfigurationID());
-               conditions.setLaunchRodLength(getLaunchRodLength());
-               conditions.setLaunchRodAngle(getLaunchRodAngle());
-               conditions.setLaunchRodDirection(getLaunchRodDirection());
-               conditions.setLaunchAltitude(getLaunchAltitude());
-               conditions.setLaunchLatitude(getLaunchLatitude());
-               
-               PinkNoiseWindModel windModel = new PinkNoiseWindModel();
-               // TODO: HIGH: Randomness source for simulation
-               windModel.setSeed(new Random().nextInt());
-               windModel.setAverage(getWindSpeedAverage());
-               windModel.setStandardDeviation(getWindSpeedDeviation());
-               conditions.setWindModel(windModel);
-               
-               conditions.setAtmosphericModel(getAtmosphericModel());
-               
-               BasicGravityModel gravityModel = new BasicGravityModel(getLaunchLatitude());
-               conditions.setGravityModel(gravityModel);
-               
-               conditions.setAerodynamicCalculator(new BarrowmanCalculator());
-               conditions.setMassCalculator(new BasicMassCalculator());
-               
-               conditions.setTimeStep(getTimeStep());
-               conditions.setMaximumAngleStep(getMaximumStepAngle());
-               
-               conditions.setCalculateExtras(getCalculateExtras());
-               
-               return conditions;
-       }
-       
-}
index 99fd475e9197ccd1c04704b2269b446a2645bbbb..8d5e27dbf94264479d1d27bea78074e420a57e0a 100644 (file)
@@ -21,6 +21,9 @@ public class RK4SimulationStepper extends AbstractSimulationStepper {
        
        private static final LogHelper log = Application.getLogger();
        
+       /** Random value with which to XOR the random seed value */
+       private static final int SEED_RANDOMIZATION = 0x23E3A01F;
+       
 
        /**
         * A recommended reasonably accurate time step.
@@ -50,8 +53,7 @@ public class RK4SimulationStepper extends AbstractSimulationStepper {
        private static final double MIN_TIME_STEP = 0.001;
        
 
-       // TODO: HIGH: Randomness source from simulation
-       private final Random random = new Random();
+       private Random random;
        
        
 
@@ -59,8 +61,6 @@ public class RK4SimulationStepper extends AbstractSimulationStepper {
        @Override
        public RK4SimulationStatus initialize(SimulationStatus original) {
                
-               log.info("Performing RK4SimulationStepper initialization");
-               
                RK4SimulationStatus status = new RK4SimulationStatus();
                
                status.copyFrom(original);
@@ -72,6 +72,8 @@ public class RK4SimulationStepper extends AbstractSimulationStepper {
                                Math.cos(sim.getLaunchRodAngle())
                                ));
                
+               this.random = new Random(original.getSimulationConditions().getRandomSeed() ^ SEED_RANDOMIZATION);
+               
                return status;
        }
        
index 1617709007569657ecf5c6b254a4eee737d7ce56..3e5820862e9f0b217a78c8d4c5bd22516796f031 100644 (file)
@@ -57,12 +57,13 @@ public class SimulationConditions implements Monitorable, Cloneable {
        private List<SimulationListener> simulationListeners = new ArrayList<SimulationListener>();
        
 
+       private int randomSeed = 0;
+       
        private int modID = 0;
        private int modIDadd = 0;
        
        
 
-
        public AerodynamicCalculator getAerodynamicCalculator() {
                return aerodynamicCalculator;
        }
@@ -240,6 +241,18 @@ public class SimulationConditions implements Monitorable, Cloneable {
        }
        
        
+
+       public int getRandomSeed() {
+               return randomSeed;
+       }
+       
+       
+       public void setRandomSeed(int randomSeed) {
+               this.randomSeed = randomSeed;
+               this.modID++;
+       }
+       
+       
        // TODO: HIGH: Make cleaner
        public List<SimulationListener> getSimulationListenerList() {
                return simulationListeners;
diff --git a/src/net/sf/openrocket/simulation/SimulationOptions.java b/src/net/sf/openrocket/simulation/SimulationOptions.java
new file mode 100644 (file)
index 0000000..05a22b1
--- /dev/null
@@ -0,0 +1,498 @@
+package net.sf.openrocket.simulation;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+
+import net.sf.openrocket.aerodynamics.BarrowmanCalculator;
+import net.sf.openrocket.masscalc.BasicMassCalculator;
+import net.sf.openrocket.models.atmosphere.AtmosphericModel;
+import net.sf.openrocket.models.atmosphere.ExtendedISAModel;
+import net.sf.openrocket.models.gravity.BasicGravityModel;
+import net.sf.openrocket.models.wind.PinkNoiseWindModel;
+import net.sf.openrocket.rocketcomponent.Rocket;
+import net.sf.openrocket.util.BugException;
+import net.sf.openrocket.util.ChangeSource;
+import net.sf.openrocket.util.MathUtil;
+
+/**
+ * A class holding simulation options in basic parameter form and which functions
+ * as a ChangeSource.  A SimulationConditions instance is generated from this class
+ * using {@link #toSimulationConditions()}.
+ * 
+ * @author Sampo Niskanen <sampo.niskanen@iki.fi>
+ */
+public class SimulationOptions implements ChangeSource, Cloneable {
+       
+       public static final double MAX_LAUNCH_ROD_ANGLE = Math.PI / 3;
+       
+       /**
+        * The ISA standard atmosphere.
+        */
+       private static final AtmosphericModel ISA_ATMOSPHERIC_MODEL = new ExtendedISAModel();
+       
+
+       private final Rocket rocket;
+       private String motorID = null;
+       
+
+       /*
+        * NOTE:  When adding/modifying parameters, they must also be added to the
+        * equals and copyFrom methods!!
+        */
+
+       // TODO: HIGH: Fetch default values from Prefs!
+       
+       private double launchRodLength = 1;
+       
+       /** Launch rod angle > 0, radians from vertical */
+       private double launchRodAngle = 0;
+       
+       /** Launch rod direction, 0 = upwind, PI = downwind. */
+       private double launchRodDirection = 0;
+       
+
+       private double windAverage = 2.0;
+       private double windTurbulence = 0.1;
+       
+       private double launchAltitude = 0;
+       private double launchLatitude = 45;
+       
+       private boolean useISA = true;
+       private double launchTemperature = ExtendedISAModel.STANDARD_TEMPERATURE;
+       private double launchPressure = ExtendedISAModel.STANDARD_PRESSURE;
+       
+
+       private double timeStep = RK4SimulationStepper.RECOMMENDED_TIME_STEP;
+       private double maximumAngle = RK4SimulationStepper.RECOMMENDED_ANGLE_STEP;
+       
+       private int randomSeed = new Random().nextInt();
+       
+       private boolean calculateExtras = true;
+       
+
+       private List<ChangeListener> listeners = new ArrayList<ChangeListener>();
+       
+       
+
+       public SimulationOptions(Rocket rocket) {
+               this.rocket = rocket;
+       }
+       
+       
+
+       public Rocket getRocket() {
+               return rocket;
+       }
+       
+       
+       public String getMotorConfigurationID() {
+               return motorID;
+       }
+       
+       /**
+        * Set the motor configuration ID.  This must be a valid motor configuration ID of
+        * the rocket, otherwise the configuration is set to <code>null</code>.
+        * 
+        * @param id    the configuration to set.
+        */
+       public void setMotorConfigurationID(String id) {
+               if (id != null)
+                       id = id.intern();
+               if (!rocket.isMotorConfigurationID(id))
+                       id = null;
+               if (id == motorID)
+                       return;
+               motorID = id;
+               fireChangeEvent();
+       }
+       
+       
+       public double getLaunchRodLength() {
+               return launchRodLength;
+       }
+       
+       public void setLaunchRodLength(double launchRodLength) {
+               if (MathUtil.equals(this.launchRodLength, launchRodLength))
+                       return;
+               this.launchRodLength = launchRodLength;
+               fireChangeEvent();
+       }
+       
+       
+       public double getLaunchRodAngle() {
+               return launchRodAngle;
+       }
+       
+       public void setLaunchRodAngle(double launchRodAngle) {
+               launchRodAngle = MathUtil.clamp(launchRodAngle, 0, MAX_LAUNCH_ROD_ANGLE);
+               if (MathUtil.equals(this.launchRodAngle, launchRodAngle))
+                       return;
+               this.launchRodAngle = launchRodAngle;
+               fireChangeEvent();
+       }
+       
+       
+       public double getLaunchRodDirection() {
+               return launchRodDirection;
+       }
+       
+       public void setLaunchRodDirection(double launchRodDirection) {
+               launchRodDirection = MathUtil.reduce180(launchRodDirection);
+               if (MathUtil.equals(this.launchRodDirection, launchRodDirection))
+                       return;
+               this.launchRodDirection = launchRodDirection;
+               fireChangeEvent();
+       }
+       
+       
+
+       public double getWindSpeedAverage() {
+               return windAverage;
+       }
+       
+       public void setWindSpeedAverage(double windAverage) {
+               if (MathUtil.equals(this.windAverage, windAverage))
+                       return;
+               this.windAverage = MathUtil.max(windAverage, 0);
+               fireChangeEvent();
+       }
+       
+       
+       public double getWindSpeedDeviation() {
+               return windAverage * windTurbulence;
+       }
+       
+       public void setWindSpeedDeviation(double windDeviation) {
+               if (windAverage < 0.1) {
+                       windAverage = 0.1;
+               }
+               setWindTurbulenceIntensity(windDeviation / windAverage);
+       }
+       
+       
+       /**
+        * Return the wind turbulence intensity (standard deviation / average).
+        * 
+        * @return  the turbulence intensity
+        */
+       public double getWindTurbulenceIntensity() {
+               return windTurbulence;
+       }
+       
+       /**
+        * Set the wind standard deviation to match the given turbulence intensity.
+        * 
+        * @param intensity   the turbulence intensity
+        */
+       public void setWindTurbulenceIntensity(double intensity) {
+               // Does not check equality so that setWindSpeedDeviation can be sure of event firing
+               this.windTurbulence = intensity;
+               fireChangeEvent();
+       }
+       
+       
+
+
+
+       public double getLaunchAltitude() {
+               return launchAltitude;
+       }
+       
+       public void setLaunchAltitude(double altitude) {
+               if (MathUtil.equals(this.launchAltitude, altitude))
+                       return;
+               this.launchAltitude = altitude;
+               fireChangeEvent();
+       }
+       
+       
+       public double getLaunchLatitude() {
+               return launchLatitude;
+       }
+       
+       public void setLaunchLatitude(double launchLatitude) {
+               launchLatitude = MathUtil.clamp(launchLatitude, -90, 90);
+               if (MathUtil.equals(this.launchLatitude, launchLatitude))
+                       return;
+               this.launchLatitude = launchLatitude;
+               fireChangeEvent();
+       }
+       
+       
+
+
+
+       public boolean isISAAtmosphere() {
+               return useISA;
+       }
+       
+       public void setISAAtmosphere(boolean isa) {
+               if (isa == useISA)
+                       return;
+               useISA = isa;
+               fireChangeEvent();
+       }
+       
+       
+       public double getLaunchTemperature() {
+               return launchTemperature;
+       }
+       
+       
+
+       public void setLaunchTemperature(double launchTemperature) {
+               if (MathUtil.equals(this.launchTemperature, launchTemperature))
+                       return;
+               this.launchTemperature = launchTemperature;
+               fireChangeEvent();
+       }
+       
+       
+
+       public double getLaunchPressure() {
+               return launchPressure;
+       }
+       
+       
+
+       public void setLaunchPressure(double launchPressure) {
+               if (MathUtil.equals(this.launchPressure, launchPressure))
+                       return;
+               this.launchPressure = launchPressure;
+               fireChangeEvent();
+       }
+       
+       
+       /**
+        * Returns an atmospheric model corresponding to the launch conditions.  The
+        * atmospheric models may be shared between different calls.
+        * 
+        * @return      an AtmosphericModel object.
+        */
+       private AtmosphericModel getAtmosphericModel() {
+               if (useISA) {
+                       return ISA_ATMOSPHERIC_MODEL;
+               }
+               return new ExtendedISAModel(launchAltitude, launchTemperature, launchPressure);
+       }
+       
+       
+       public double getTimeStep() {
+               return timeStep;
+       }
+       
+       public void setTimeStep(double timeStep) {
+               if (MathUtil.equals(this.timeStep, timeStep))
+                       return;
+               this.timeStep = timeStep;
+               fireChangeEvent();
+       }
+       
+       public double getMaximumStepAngle() {
+               return maximumAngle;
+       }
+       
+       public void setMaximumStepAngle(double maximumAngle) {
+               maximumAngle = MathUtil.clamp(maximumAngle, 1 * Math.PI / 180, 20 * Math.PI / 180);
+               if (MathUtil.equals(this.maximumAngle, maximumAngle))
+                       return;
+               this.maximumAngle = maximumAngle;
+               fireChangeEvent();
+       }
+       
+       
+
+       public boolean getCalculateExtras() {
+               return calculateExtras;
+       }
+       
+       
+
+       public void setCalculateExtras(boolean calculateExtras) {
+               if (this.calculateExtras == calculateExtras)
+                       return;
+               this.calculateExtras = calculateExtras;
+               fireChangeEvent();
+       }
+       
+       
+
+       public int getRandomSeed() {
+               return randomSeed;
+       }
+       
+       public void setRandomSeed(int randomSeed) {
+               if (this.randomSeed == randomSeed) {
+                       return;
+               }
+               this.randomSeed = randomSeed;
+               /*
+                * This does not fire an event since we don't want to invalidate simulation results
+                * due to changing the seed value.  This needs to be revisited if the user is ever
+                * allowed to select the seed value.
+                */
+               //              fireChangeEvent();
+       }
+       
+       /**
+        * Randomize the random seed value.
+        */
+       public void randomizeSeed() {
+               this.randomSeed = new Random().nextInt();
+               //              fireChangeEvent();
+       }
+       
+       
+
+       @Override
+       public SimulationOptions clone() {
+               try {
+                       SimulationOptions copy = (SimulationOptions) super.clone();
+                       copy.listeners = new ArrayList<ChangeListener>();
+                       return copy;
+               } catch (CloneNotSupportedException e) {
+                       throw new BugException(e);
+               }
+       }
+       
+       
+       public void copyFrom(SimulationOptions src) {
+               
+               if (this.rocket == src.rocket) {
+                       
+                       this.motorID = src.motorID;
+                       
+               } else {
+                       
+                       if (src.rocket.hasMotors(src.motorID)) {
+                               // Try to find a matching motor ID
+                               String motorDesc = src.rocket.getMotorConfigurationDescription(src.motorID);
+                               String matchID = null;
+                               
+                               for (String id : this.rocket.getMotorConfigurationIDs()) {
+                                       if (motorDesc.equals(this.rocket.getMotorConfigurationDescription(id))) {
+                                               matchID = id;
+                                               break;
+                                       }
+                               }
+                               
+                               this.motorID = matchID;
+                       } else {
+                               this.motorID = null;
+                       }
+               }
+               
+               this.launchAltitude = src.launchAltitude;
+               this.launchLatitude = src.launchLatitude;
+               this.launchPressure = src.launchPressure;
+               this.launchRodAngle = src.launchRodAngle;
+               this.launchRodDirection = src.launchRodDirection;
+               this.launchRodLength = src.launchRodLength;
+               this.launchTemperature = src.launchTemperature;
+               this.maximumAngle = src.maximumAngle;
+               this.timeStep = src.timeStep;
+               this.windAverage = src.windAverage;
+               this.windTurbulence = src.windTurbulence;
+               this.calculateExtras = src.calculateExtras;
+               this.randomSeed = src.randomSeed;
+               
+               fireChangeEvent();
+       }
+       
+       
+
+       /**
+        * Compares whether the two simulation conditions are equal.  The two are considered
+        * equal if the rocket, motor id and all variables are equal.
+        */
+       @Override
+       public boolean equals(Object other) {
+               if (!(other instanceof SimulationOptions))
+                       return false;
+               SimulationOptions o = (SimulationOptions) other;
+               return ((this.rocket == o.rocket) &&
+                               this.motorID.equals(o.motorID) &&
+                               MathUtil.equals(this.launchAltitude, o.launchAltitude) &&
+                               MathUtil.equals(this.launchLatitude, o.launchLatitude) &&
+                               MathUtil.equals(this.launchPressure, o.launchPressure) &&
+                               MathUtil.equals(this.launchRodAngle, o.launchRodAngle) &&
+                               MathUtil.equals(this.launchRodDirection, o.launchRodDirection) &&
+                               MathUtil.equals(this.launchRodLength, o.launchRodLength) &&
+                               MathUtil.equals(this.launchTemperature, o.launchTemperature) &&
+                               MathUtil.equals(this.maximumAngle, o.maximumAngle) &&
+                               MathUtil.equals(this.timeStep, o.timeStep) &&
+                               MathUtil.equals(this.windAverage, o.windAverage) &&
+                               MathUtil.equals(this.windTurbulence, o.windTurbulence) &&
+                               this.calculateExtras == o.calculateExtras && this.randomSeed == o.randomSeed);
+       }
+       
+       /**
+        * Hashcode method compatible with {@link #equals(Object)}.
+        */
+       @Override
+       public int hashCode() {
+               if (motorID == null)
+                       return rocket.hashCode();
+               return rocket.hashCode() + motorID.hashCode();
+       }
+       
+       @Override
+       public void addChangeListener(ChangeListener listener) {
+               listeners.add(listener);
+       }
+       
+       @Override
+       public void removeChangeListener(ChangeListener listener) {
+               listeners.remove(listener);
+       }
+       
+       private final ChangeEvent event = new ChangeEvent(this);
+       
+       private void fireChangeEvent() {
+               ChangeListener[] array = listeners.toArray(new ChangeListener[0]);
+               
+               for (int i = array.length - 1; i >= 0; i--) {
+                       array[i].stateChanged(event);
+               }
+       }
+       
+       
+       // TODO: HIGH: Clean up
+       public SimulationConditions toSimulationConditions() {
+               SimulationConditions conditions = new SimulationConditions();
+               
+               conditions.setRocket((Rocket) getRocket().copy());
+               conditions.setMotorConfigurationID(getMotorConfigurationID());
+               conditions.setLaunchRodLength(getLaunchRodLength());
+               conditions.setLaunchRodAngle(getLaunchRodAngle());
+               conditions.setLaunchRodDirection(getLaunchRodDirection());
+               conditions.setLaunchAltitude(getLaunchAltitude());
+               conditions.setLaunchLatitude(getLaunchLatitude());
+               conditions.setRandomSeed(randomSeed);
+               
+               PinkNoiseWindModel windModel = new PinkNoiseWindModel(randomSeed);
+               windModel.setAverage(getWindSpeedAverage());
+               windModel.setStandardDeviation(getWindSpeedDeviation());
+               conditions.setWindModel(windModel);
+               
+               conditions.setAtmosphericModel(getAtmosphericModel());
+               
+               BasicGravityModel gravityModel = new BasicGravityModel(getLaunchLatitude());
+               conditions.setGravityModel(gravityModel);
+               
+               conditions.setAerodynamicCalculator(new BarrowmanCalculator());
+               conditions.setMassCalculator(new BasicMassCalculator());
+               
+               conditions.setTimeStep(getTimeStep());
+               conditions.setMaximumAngleStep(getMaximumStepAngle());
+               
+               conditions.setCalculateExtras(getCalculateExtras());
+               
+               return conditions;
+       }
+       
+}
diff --git a/src/net/sf/openrocket/simulation/exception/MotorIgnitionException.java b/src/net/sf/openrocket/simulation/exception/MotorIgnitionException.java
new file mode 100644 (file)
index 0000000..fd9e60a
--- /dev/null
@@ -0,0 +1,19 @@
+package net.sf.openrocket.simulation.exception;
+
+/**
+ * An exception signifying that the simulation failed because no motors were
+ * defined or ignited in the rocket.
+ * 
+ * @author Sampo Niskanen <sampo.niskanen@iki.fi>
+ */
+public class MotorIgnitionException extends SimulationLaunchException {
+       
+       public MotorIgnitionException(String message) {
+               super(message);
+       }
+       
+       public MotorIgnitionException(String message, Throwable cause) {
+               super(message, cause);
+       }
+       
+}
index 1c840e35ffdb344cbd66177f57c77315f8e21b48..df9f3862d696d05cefbea81ce9a16bffa5cee98f 100644 (file)
@@ -7,21 +7,13 @@ package net.sf.openrocket.simulation.exception;
  * @author Sampo Niskanen <sampo.niskanen@iki.fi>
  */
 public class SimulationLaunchException extends SimulationException {
-
-       public SimulationLaunchException() {
-
-       }
-
+       
        public SimulationLaunchException(String message) {
                super(message);
        }
-
-       public SimulationLaunchException(Throwable cause) {
-               super(cause);
-       }
-
+       
        public SimulationLaunchException(String message, Throwable cause) {
                super(message, cause);
        }
-
+       
 }
index a7847d32b82692e6bbc04ca3fc1d73915e24a7e6..bb8b20f771c452493804f23888207acc372cf4b6 100644 (file)
@@ -89,10 +89,6 @@ public final class Application {
         * @return      a translator.
         */
        public static Translator getTranslator() {
-               if (baseTranslator instanceof DebugTranslator) {
-                       return baseTranslator;
-               }
-               
                Translator t = baseTranslator;
                t = new ClassBasedTranslator(t, 1);
                t = new ExceptionSuppressingTranslator(t);
index c33c4b6ff4f570573a11ecd3f33c04e251d4306d..cc93cb5114eb846d93c83529e078398ed1b314af 100644 (file)
@@ -1,16 +1,12 @@
 package net.sf.openrocket.unit;
 
-import java.util.Collection;
-import java.util.Collections;
 import java.util.Iterator;
 
-import javax.swing.event.ChangeEvent;
-import javax.swing.event.ChangeListener;
-
 import net.sf.openrocket.rocketcomponent.Configuration;
 import net.sf.openrocket.rocketcomponent.Rocket;
 import net.sf.openrocket.rocketcomponent.RocketComponent;
 import net.sf.openrocket.rocketcomponent.SymmetricComponent;
+import net.sf.openrocket.util.BugException;
 import net.sf.openrocket.util.MathUtil;
 
 
@@ -21,19 +17,14 @@ public class CaliberUnit extends GeneralUnit {
        private final Configuration configuration;
        private final Rocket rocket;
        
-       private double caliber = -1;
+       private int rocketModId = -1;
+       private int configurationModId = -1;
        
-
-       /* Listener for rocket and configuration, resets the caliber to -1. */
-       private final ChangeListener listener = new ChangeListener() {
-               @Override
-               public void stateChanged(ChangeEvent e) {
-                       caliber = -1;
-               }
-       };
+       private double caliber = -1;
        
        
 
+
        public CaliberUnit(Configuration configuration) {
                super(1.0, "cal");
                this.configuration = configuration;
@@ -42,7 +33,6 @@ public class CaliberUnit extends GeneralUnit {
                        this.rocket = null;
                } else {
                        this.rocket = configuration.getRocket();
-                       configuration.addChangeListener(listener);
                }
        }
        
@@ -50,53 +40,94 @@ public class CaliberUnit extends GeneralUnit {
                super(1.0, "cal");
                this.configuration = null;
                this.rocket = rocket;
-               if (rocket != null) {
-                       rocket.addChangeListener(listener);
+       }
+       
+       public CaliberUnit(double reference) {
+               super(1.0, "cal");
+               this.configuration = null;
+               this.rocket = null;
+               this.caliber = reference;
+               
+               if (reference <= 0) {
+                       throw new IllegalArgumentException("Illegal reference = " + reference);
                }
        }
        
        
        @Override
        public double fromUnit(double value) {
-               if (caliber < 0)
-                       calculateCaliber();
+               checkCaliber();
                
                return value * caliber;
        }
        
        @Override
        public double toUnit(double value) {
-               if (caliber < 0)
-                       calculateCaliber();
+               checkCaliber();
                
                return value / caliber;
        }
        
        
-       // TODO: HIGH:  Check caliber calculation method...
-       private void calculateCaliber() {
-               caliber = 0;
-               
-               Iterator<RocketComponent> iterator;
-               if (configuration != null) {
-                       iterator = configuration.iterator();
-               } else if (rocket != null) {
-                       iterator = rocket.iterator(false);
-               } else {
-                       Collection<RocketComponent> set = Collections.emptyList();
-                       iterator = set.iterator();
+
+       private void checkCaliber() {
+               if (configuration != null && configuration.getModID() != configurationModId) {
+                       caliber = -1;
+                       configurationModId = configuration.getModID();
+               }
+               if (rocket != null && rocket.getModID() != rocketModId) {
+                       caliber = -1;
+                       rocketModId = rocket.getModID();
+               }
+               if (caliber < 0) {
+                       if (configuration != null) {
+                               caliber = calculateCaliber(configuration);
+                       } else if (rocket != null) {
+                               caliber = calculateCaliber(rocket);
+                       } else {
+                               throw new BugException("Both rocket and configuration are null");
+                       }
                }
+       }
+       
+       
+       /**
+        * Calculate the caliber of a rocket configuration.
+        * 
+        * @param config        the rocket configuration
+        * @return                      the caliber of the rocket, or the default caliber.
+        */
+       public static double calculateCaliber(Configuration config) {
+               return calculateCaliber(config.iterator());
+       }
+       
+       /**
+        * Calculate the caliber of a rocket.
+        * 
+        * @param rocket        the rocket
+        * @return                      the caliber of the rocket, or the default caliber.
+        */
+       public static double calculateCaliber(Rocket rocket) {
+               return calculateCaliber(rocket.iterator());
+       }
+       
+       
+
+       private static double calculateCaliber(Iterator<RocketComponent> iterator) {
+               double cal = 0;
                
                while (iterator.hasNext()) {
                        RocketComponent c = iterator.next();
                        if (c instanceof SymmetricComponent) {
                                double r1 = ((SymmetricComponent) c).getForeRadius() * 2;
                                double r2 = ((SymmetricComponent) c).getAftRadius() * 2;
-                               caliber = MathUtil.max(caliber, r1, r2);
+                               cal = MathUtil.max(cal, r1, r2);
                        }
                }
                
-               if (caliber <= 0)
-                       caliber = DEFAULT_CALIBER;
+               if (cal < 0.0001)
+                       cal = DEFAULT_CALIBER;
+               
+               return cal;
        }
 }
index 0f75d827574a985c5dd6d42ea884e5167d43baf7..920e766484c0a3e05b27cba80d43e364e483ae7a 100644 (file)
@@ -31,6 +31,11 @@ public class UnitGroup {
        
        public static final UnitGroup UNITS_AREA;
        public static final UnitGroup UNITS_STABILITY;
+       /**
+        * This unit group contains only the caliber unit that never scales the originating "SI" value.
+        * It can be used in cases where the originating value is already in calibers to obtains the correct unit.
+        */
+       public static final UnitGroup UNITS_STABILITY_CALIBERS;
        public static final UnitGroup UNITS_VELOCITY;
        public static final UnitGroup UNITS_ACCELERATION;
        public static final UnitGroup UNITS_MASS;
@@ -111,6 +116,10 @@ public class UnitGroup {
                UNITS_STABILITY.addUnit(new CaliberUnit((Rocket) null));
                UNITS_STABILITY.setDefaultUnit(3);
                
+               UNITS_STABILITY_CALIBERS = new UnitGroup();
+               UNITS_STABILITY_CALIBERS.addUnit(new GeneralUnit(1, "cal"));
+               
+
                UNITS_VELOCITY = new UnitGroup();
                UNITS_VELOCITY.addUnit(new GeneralUnit(1, "m/s"));
                UNITS_VELOCITY.addUnit(new GeneralUnit(1 / 3.6, "km/h"));
@@ -204,8 +213,11 @@ public class UnitGroup {
                
                UNITS_RELATIVE = new UnitGroup();
                UNITS_RELATIVE.addUnit(new FixedPrecisionUnit("" + ZWSP, 0.01, 1.0));
-               UNITS_RELATIVE.addUnit(new FixedPrecisionUnit("%", 1, 0.01));
+               UNITS_RELATIVE.addUnit(new GeneralUnit(0.01, "%"));
                UNITS_RELATIVE.addUnit(new FixedPrecisionUnit("" + PERMILLE, 1, 0.001));
+               //              UNITS_RELATIVE.addUnit(new FixedPrecisionUnit("" + ZWSP, 0.01, 1.0));
+               //              UNITS_RELATIVE.addUnit(new FixedPrecisionUnit("%", 1, 0.01));
+               //              UNITS_RELATIVE.addUnit(new FixedPrecisionUnit("" + PERMILLE, 1, 0.001));
                UNITS_RELATIVE.setDefaultUnit(1);
                
 
@@ -309,22 +321,44 @@ public class UnitGroup {
        }
        
        
-
+       /**
+        * Return a UnitGroup for stability units based on the rocket.
+        * 
+        * @param rocket        the rocket from which to calculate the caliber
+        * @return                      the unit group
+        */
        public static UnitGroup stabilityUnits(Rocket rocket) {
                return new StabilityUnitGroup(rocket);
        }
        
        
+       /**
+        * Return a UnitGroup for stability units based on the rocket configuration.
+        * 
+        * @param config        the rocket configuration from which to calculate the caliber
+        * @return                      the unit group
+        */
        public static UnitGroup stabilityUnits(Configuration config) {
                return new StabilityUnitGroup(config);
        }
        
        
+       /**
+        * Return a UnitGroup for stability units based on a constant caliber.
+        * 
+        * @param reference     the constant reference length
+        * @return                      the unit group
+        */
+       public static UnitGroup stabilityUnits(double reference) {
+               return new StabilityUnitGroup(reference);
+       }
+       
+       
        //////////////////////////////////////////////////////
        
 
-       private ArrayList<Unit> units = new ArrayList<Unit>();
-       private int defaultUnit = 0;
+       protected ArrayList<Unit> units = new ArrayList<Unit>();
+       protected int defaultUnit = 0;
        
        public int getUnitCount() {
                return units.size();
@@ -391,18 +425,10 @@ public class UnitGroup {
                return units.indexOf(u);
        }
        
-       public void addUnit(Unit u) {
+       private void addUnit(Unit u) {
                units.add(u);
        }
        
-       public void addUnit(int n, Unit u) {
-               units.add(n, u);
-       }
-       
-       public void removeUnit(int n) {
-               units.remove(n);
-       }
-       
        public boolean contains(Unit u) {
                return units.contains(u);
        }
@@ -508,80 +534,33 @@ public class UnitGroup {
         */
        private static class StabilityUnitGroup extends UnitGroup {
                
-               private final CaliberUnit caliberUnit;
-               
+               public StabilityUnitGroup(double ref) {
+                       this(new CaliberUnit(ref));
+               }
                
                public StabilityUnitGroup(Rocket rocket) {
-                       caliberUnit = new CaliberUnit(rocket);
+                       this(new CaliberUnit(rocket));
                }
                
                public StabilityUnitGroup(Configuration config) {
-                       caliberUnit = new CaliberUnit(config);
-               }
-               
-               
-               ////  Modify CaliberUnit to use local variable
-               
-               @Override
-               public Unit getDefaultUnit() {
-                       return getUnit(UNITS_STABILITY.getDefaultUnitIndex());
+                       this(new CaliberUnit(config));
                }
                
-               @Override
-               public Unit getUnit(int n) {
-                       Unit u = UNITS_STABILITY.getUnit(n);
-                       if (u instanceof CaliberUnit) {
-                               return caliberUnit;
-                       }
-                       return u;
-               }
-               
-               @Override
-               public int getUnitIndex(Unit u) {
-                       if (u instanceof CaliberUnit) {
-                               for (int i = 0; i < UNITS_STABILITY.getUnitCount(); i++) {
-                                       if (UNITS_STABILITY.getUnit(i) instanceof CaliberUnit)
-                                               return i;
+               private StabilityUnitGroup(CaliberUnit caliberUnit) {
+                       this.units.addAll(UnitGroup.UNITS_STABILITY.units);
+                       this.defaultUnit = UnitGroup.UNITS_STABILITY.defaultUnit;
+                       for (int i = 0; i < units.size(); i++) {
+                               if (units.get(i) instanceof CaliberUnit) {
+                                       units.set(i, caliberUnit);
                                }
                        }
-                       return UNITS_STABILITY.getUnitIndex(u);
                }
                
                
-
-               ////  Pass on to UNITS_STABILITY
-               
-               @Override
-               public int getDefaultUnitIndex() {
-                       return UNITS_STABILITY.getDefaultUnitIndex();
-               }
-               
                @Override
                public void setDefaultUnit(int n) {
+                       super.setDefaultUnit(n);
                        UNITS_STABILITY.setDefaultUnit(n);
                }
-               
-               @Override
-               public int getUnitCount() {
-                       return UNITS_STABILITY.getUnitCount();
-               }
-               
-               
-               ////  Unsupported methods
-               
-               @Override
-               public void addUnit(int n, Unit u) {
-                       throw new UnsupportedOperationException("StabilityUnitGroup must not be modified");
-               }
-               
-               @Override
-               public void addUnit(Unit u) {
-                       throw new UnsupportedOperationException("StabilityUnitGroup must not be modified");
-               }
-               
-               @Override
-               public void removeUnit(int n) {
-                       throw new UnsupportedOperationException("StabilityUnitGroup must not be modified");
-               }
        }
 }
index 278b815a9264a63f7fafb179dc11714c68d7d91b..65382523cf98e783e80d29b1e54d6d9b222eea3d 100644 (file)
@@ -3,7 +3,7 @@ package net.sf.openrocket.unit;
 import net.sf.openrocket.util.MathUtil;
 
 /**
- * A class representing an SI value and a unit.  The toString() method yields the
+ * An immutable class representing an SI value and a unit.  The toString() method yields the
  * current value in the current units.  This class may be used to encapsulate
  * a sortable value for example for tables.  The sorting is performed by the
  * value in the current units, ignoring the unit.
@@ -12,8 +12,8 @@ import net.sf.openrocket.util.MathUtil;
  */
 public class Value implements Comparable<Value> {
        
-       private double value;
-       private Unit unit;
+       private final double value;
+       private final Unit unit;
        
        
        /**
@@ -44,7 +44,7 @@ public class Value implements Comparable<Value> {
        
        
        /**
-        * Get the value of this object.
+        * Get the value of this object (in SI units).
         * 
         * @return the value
         */
@@ -52,16 +52,8 @@ public class Value implements Comparable<Value> {
                return value;
        }
        
-       /**
-        * Set the value of this object.
-        * 
-        * @param value the value to set
-        */
-       public void setValue(double value) {
-               this.value = value;
-       }
-       
        
+
        /**
         * Get the value of this object in the current units.
         * 
@@ -72,16 +64,6 @@ public class Value implements Comparable<Value> {
        }
        
        
-       /**
-        * Set the value of this object in the current units.
-        * 
-        * @param value         the value in current units.
-        */
-       public void setUnitValue(double value) {
-               this.value = unit.fromUnit(value);
-       }
-       
-       
        /**
         * Get the unit of this object.
         * 
@@ -91,18 +73,6 @@ public class Value implements Comparable<Value> {
                return unit;
        }
        
-       /**
-        * Set the value of this object.
-        * 
-        * @param unit the unit to set (<code>null</code> not allowed)
-        */
-       public void setUnit(Unit unit) {
-               if (unit == null) {
-                       throw new IllegalArgumentException("unit is null");
-               }
-               this.unit = unit;
-       }
-       
        
        /**
         * Return a string formatted using the {@link Unit#toStringUnit(double)} method
index 08d2f827a2f07cff859116d027c50ad52dae30a0..1920bfa87c99403695b8d0181608359bd365e292 100644 (file)
@@ -6,7 +6,7 @@ package net.sf.openrocket.util;
  * @author Sampo Niskanen <sampo.niskanen@iki.fi>
  */
 public class Chars {
-
+       
        /** The fraction 1/2 */
        public static final char FRAC12 = '\u00BD';
        /** The fraction 1/4 */
@@ -37,7 +37,7 @@ public class Chars {
        
        /** Micro sign (Greek letter mu) */
        public static final char MICRO = '\u00B5';
-
+       
        /** Alpha */
        public static final char ALPHA = '\u03b1';
        /** Theta */
@@ -47,4 +47,10 @@ public class Chars {
        public static final char COPY = '\u00A9';
        /** A centered bullet */
        public static final char BULLET = '\u2022';
+       
+       /** Left arrow (light) */
+       public static final char LEFT_ARROW = '\u2190';
+       /** Right arrow (light) */
+       public static final char RIGHT_ARROW = '\u2192';
+       
 }
index 909cf273956752da172590c0ddd66f91e4bff8df..3bc981aaa8d1a618b2bf0067fa320fd5ea236f0f 100644 (file)
@@ -206,7 +206,7 @@ public final class Coordinate implements Serializable {
         */
        public double length() {
                if (length < 0) {
-                       length = Math.sqrt(x * x + y * y + z * z);
+                       length = MathUtil.safeSqrt(x * x + y * y + z * z);
                }
                return length;
        }
index 6a8216ff0863497dc2a2deea5f1bf9ea00545a54..0929c3280a16bf847553a0a3341c9b377aa921b3 100644 (file)
@@ -24,7 +24,7 @@ public final class FileHelper {
        private static final Translator trans = Application.getTranslator();
        
 
-       // TODO: MEDIUM: Rename translation keys
+       // TODO: HIGH: Rename translation keys
        
        /** File filter for any rocket designs (*.ork, *.rkt) */
        public static final FileFilter ALL_DESIGNS_FILTER =
@@ -43,7 +43,12 @@ public final class FileHelper {
        public static final FileFilter PDF_FILTER =
                        new SimpleFileFilter(trans.get("filetypes.pdf"), ".pdf");
        
+       /** File filter for CSV files (*.csv) */
+       public static final FileFilter CSV_FILE_FILTER =
+                       new SimpleFileFilter(trans.get("SimExpPan.desc"), ".csv");
        
+       
+
 
 
        private FileHelper() {
index 10d632e2737b5761a93baccebcdfe2ee4fbc2581..831f2381afc44b53f13d84fb536cfc38d87572dd 100644 (file)
@@ -52,6 +52,7 @@ import javax.swing.SpinnerModel;
 import javax.swing.SpinnerNumberModel;
 import javax.swing.SwingUtilities;
 import javax.swing.UIManager;
+import javax.swing.border.TitledBorder;
 import javax.swing.event.ChangeListener;
 import javax.swing.table.AbstractTableModel;
 import javax.swing.table.DefaultTableColumnModel;
@@ -265,6 +266,32 @@ public class GUIUtil {
        }
        
        
+       /**
+        * Changes the style of the font of the specified border.
+        * 
+        * @param border                the component for which to change the font
+        * @param style                 the change in the font style
+        */
+       public static void changeFontStyle(TitledBorder border, int style) {
+               /*
+                * There's been an NPE caused by the font changing, this is debug for it.
+                */
+               if (border == null) {
+                       throw new BugException("border is null");
+               }
+               Font font = border.getTitleFont();
+               if (font == null) {
+                       throw new BugException("Border font is null");
+               }
+               font = font.deriveFont(style);
+               if (font == null) {
+                       throw new BugException("Derived font is null");
+               }
+               border.setTitleFont(font);
+       }
+       
+       
+
        /**
         * Traverses recursively the component tree, and sets all applicable component 
         * models to null, so as to remove the listener connections.  After calling this
index 58585d7717c66d54b62c2d18bda811d966939fc0..75b18943ad1d53485f8f7c5073c6ac4c790ccc36 100644 (file)
@@ -6,7 +6,12 @@ import java.util.Collections;
 import java.util.Comparator;
 import java.util.List;
 
+import net.sf.openrocket.logging.LogHelper;
+import net.sf.openrocket.startup.Application;
+
 public class MathUtil {
+       private static final LogHelper log = Application.getLogger();
+       
        public static final double EPSILON = 0.00000001; // 10mm^3 in m^3
        
        /**
@@ -192,6 +197,25 @@ public class MathUtil {
        }
        
        
+       /**
+        * Return the square root of a value.  If the value is negative, zero is returned.
+        * This is safer in cases where rounding errors might make a value slightly negative.
+        * 
+        * @param d             the value of which the square root is to be taken.
+        * @return              the square root of the value.
+        */
+       public static double safeSqrt(double d) {
+               if (d < 0) {
+                       if (d < 0.01) {
+                               log.warn(1, "Attempting to compute sqrt(" + d + ")");
+                       }
+                       return 0;
+               }
+               return Math.sqrt(d);
+       }
+       
+       
+
        public static boolean equals(double a, double b) {
                double absb = Math.abs(b);
                
diff --git a/src/net/sf/openrocket/util/MutableCoordinate.java b/src/net/sf/openrocket/util/MutableCoordinate.java
deleted file mode 100644 (file)
index 02af935..0000000
+++ /dev/null
@@ -1,312 +0,0 @@
-package net.sf.openrocket.util;
-
-import java.io.Serializable;
-
-/**
- * An immutable class of weighted coordinates.  The weights are non-negative.
- * 
- * Can also be used as non-weighted coordinates with weight=0.
- * 
- * @author Sampo Niskanen <sampo.niskanen@iki.fi>
- */
-public final class MutableCoordinate implements Serializable {
-       public static final MutableCoordinate NUL = new MutableCoordinate(0,0,0,0);
-       public static final MutableCoordinate NaN = new MutableCoordinate(Double.NaN,Double.NaN,
-                       Double.NaN,Double.NaN);
-       public static final double COMPARISON_DELTA = 0.000001;
-       private double x,y,z;
-       private double weight;
-       
-       
-       /* Count and report the number of times a Coordinate is constructed: */
-//     private static int count=0;
-//     {
-//             count++;
-//             if ((count % 1000) == 0) {
-//                     System.out.println("Coordinate instantiated "+count+" times");
-//             }
-//     }
-       
-
-       public MutableCoordinate() {
-               x=0;
-               y=0;
-               z=0;
-               weight=0;
-       }
-       
-       public MutableCoordinate(double x) {
-               this.x = x;
-               this.y = 0;
-               this.z = 0;
-               weight = 0;
-       }
-       
-       public MutableCoordinate(double x, double y) {
-               this.x = x;
-               this.y = y;
-               this.z = 0;
-               weight = 0;
-       }
-       
-       public MutableCoordinate(double x, double y, double z) {
-               this.x = x;
-               this.y = y;
-               this.z = z;
-               weight = 0;
-       }
-       public MutableCoordinate(double x, double y, double z, double w) {
-               this.x = x;
-               this.y = y;
-               this.z = z;
-               this.weight=w;
-       }
-
-       
-       public boolean isWeighted() {
-               return (weight != 0);
-       }
-       
-       public boolean isNaN() {
-               return Double.isNaN(x) || Double.isNaN(y) || Double.isNaN(z) || Double.isNaN(weight);
-       }
-       
-       public MutableCoordinate setX(double x) {
-               return new MutableCoordinate(x,this.y,this.z,this.weight);
-       }
-       
-       public MutableCoordinate setY(double y) {
-               return new MutableCoordinate(this.x,y,this.z,this.weight);
-       }
-       
-       public MutableCoordinate setZ(double z) {
-               return new MutableCoordinate(this.x,this.y,z,this.weight);
-       }
-       
-       public MutableCoordinate setWeight(double weight) {
-               return new MutableCoordinate(this.x, this.y, this.z, weight);
-       }
-       
-       public MutableCoordinate setXYZ(MutableCoordinate c) {
-               return new MutableCoordinate(c.x, c.y, c.z, this.weight);
-       }
-       
-       public double getX() {
-               return x;
-       }
-       public double getY() {
-               return y;
-       }
-       public double getZ() {
-               return z;
-       }
-
-       
-       /**
-        * Add the coordinate and weight of two coordinates.
-        * 
-        * @param other  the other <code>Coordinate</code>
-        * @return               the sum of the coordinates
-        */
-       public MutableCoordinate add(MutableCoordinate other) {
-               this.x += other.x;
-               this.y += other.y;
-               this.z += other.z;
-               this.weight += other.weight;
-               return this;
-       }
-       
-       public MutableCoordinate add(double x, double y, double z) {
-               this.x += x;
-               this.y += y;
-               this.z += z;
-               return this;
-       }
-
-       public MutableCoordinate add(double x, double y, double z, double weight) {
-               return new MutableCoordinate(this.x+x, this.y+y, this.z+z, this.weight+weight);
-       }
-
-       /**
-        * Subtract a Coordinate from this Coordinate.  The weight of the resulting Coordinate
-        * is the same as of this Coordinate, the weight of the argument is ignored.
-        * 
-        * @param other  Coordinate to subtract from this.
-        * @return  The result
-        */
-       public MutableCoordinate sub(MutableCoordinate other) {
-               return new MutableCoordinate(this.x-other.x, this.y-other.y, this.z-other.z, this.weight);
-       }
-
-       /**
-        * Subtract the specified values from this Coordinate.  The weight of the result
-        * is the same as the weight of this Coordinate.
-        * 
-        * @param x     x value to subtract
-        * @param y             y value to subtract
-        * @param z             z value to subtract
-        * @return              the result.
-        */
-       public MutableCoordinate sub(double x, double y, double z) {
-               return new MutableCoordinate(this.x - x, this.y - y, this.z - z, this.weight);
-       }
-       
-       
-       /**
-        * Multiply the <code>Coordinate</code> with a scalar.  All coordinates and the
-        * weight are multiplied by the given scalar.
-
-        * @param m  Factor to multiply by.
-        * @return   The product. 
-        */
-       public MutableCoordinate multiply(double m) {
-               return new MutableCoordinate(this.x*m, this.y*m, this.z*m, this.weight*m);
-       }
-
-       /**
-        * Dot product of two Coordinates, taken as vectors.  Equal to
-        * x1*x2+y1*y2+z1*z2
-        * @param other  Coordinate to take product with.
-        * @return   The dot product.
-        */
-       public double dot(MutableCoordinate other) {
-               return this.x*other.x + this.y*other.y + this.z*other.z;
-       }
-       /**
-        * Dot product of two Coordinates.
-        */
-       public static double dot(MutableCoordinate v1, MutableCoordinate v2) {
-               return v1.x*v2.x + v1.y*v2.y + v1.z*v2.z;
-       }
-
-       /**
-        * Distance from the origin to the Coordinate.
-        */
-       public double length() {
-               return Math.sqrt(x*x+y*y+z*z);
-       }
-       
-       /**
-        * Square of the distance from the origin to the Coordinate.
-        */
-       public double length2() {
-               return x*x+y*y+z*z;
-       }
-       
-       /**
-        * Returns a new coordinate which has the same direction from the origin as this
-        * coordinate but is at a distance of one.  If this coordinate is the origin,
-        * this method throws an <code>IllegalStateException</code>.  The weight of the
-        * coordinate is unchanged.
-        * 
-        * @return   the coordinate normalized to distance one of the origin.
-        * @throws   IllegalStateException  if this coordinate is the origin.
-        */
-       public MutableCoordinate normalize() {
-               double l = length();
-               if (l < 0.0000001) {
-                       throw new IllegalStateException("Cannot normalize zero coordinate");
-               }
-               return new MutableCoordinate(x/l, y/l, z/l, weight);
-       }
-       
-       
-       
-       
-       /**
-        * Weighted average of two coordinates.  If either of the weights are positive,
-        * the result is the weighted average of the coordinates and the weight is the sum
-        * of the original weights.  If the sum of the weights is zero (and especially if
-        * both of the weights are zero), the result is the unweighted average of the 
-        * coordinates with weight zero.
-        * <p>
-        * If <code>other</code> is <code>null</code> then this <code>Coordinate</code> is
-        * returned.
-        */
-       public MutableCoordinate average(MutableCoordinate other) {
-               double x,y,z,w;
-               
-               if (other == null)
-                       return this;
-               
-               w = this.weight + other.weight;
-               if (MathUtil.equals(w, 0)) {
-                       x = (this.x+other.x)/2;
-                       y = (this.y+other.y)/2;
-                       z = (this.z+other.z)/2;
-                       w = 0;
-               } else {
-                       x = (this.x*this.weight + other.x*other.weight)/w;
-                       y = (this.y*this.weight + other.y*other.weight)/w;
-                       z = (this.z*this.weight + other.z*other.weight)/w;
-               }
-               return new MutableCoordinate(x,y,z,w);
-       }
-       
-       
-       /**
-        * Tests whether the coordinates (not weight!) are the same.
-        * 
-        * Compares only the (x,y,z) coordinates, NOT the weight.  Coordinate comparison is
-        * done to the precision of COMPARISON_DELTA.
-        * 
-        * @param other  Coordinate to compare to.
-        * @return  true if the coordinates are equal
-        */
-       @Override
-       public boolean equals(Object other) {
-               if (!(other instanceof MutableCoordinate))
-                       return false;
-               
-               final MutableCoordinate c = (MutableCoordinate)other;
-               return (MathUtil.equals(this.x, c.x) &&
-                               MathUtil.equals(this.y, c.y) &&
-                               MathUtil.equals(this.z, c.z));
-       }
-       
-       /**
-        * Hash code method compatible with {@link #equals(Object)}.
-        */
-       @Override
-       public int hashCode() {
-               return (int)((x+y+z)*100000);
-       }
-       
-       
-       @Override
-       public String toString() {
-               if (isWeighted())
-                       return String.format("(%.3f,%.3f,%.3f,w=%.3f)", x,y,z,weight);
-               else
-                       return String.format("(%.3f,%.3f,%.3f)", x,y,z);
-       }
-       
-       
-       
-       public static void main(String[] arg) {
-               double a=1.2;
-               double x;
-               MutableCoordinate c;
-               long t1, t2;
-               
-               x = 0;
-               t1 = System.nanoTime();
-               for (int i=0; i < 100000000; i++) {
-                       x = x + a;
-               }
-               t2 = System.nanoTime();
-               System.out.println("Value: "+x);
-               System.out.println("Plain addition: "+ ((t2-t1+500000)/1000000) + " ms");
-               
-               c = MutableCoordinate.NUL;
-               t1 = System.nanoTime();
-               for (int i=0; i < 100000000; i++) {
-                       c = c.add(a,0,0);
-               }
-               t2 = System.nanoTime();
-               System.out.println("Value: "+c.x);
-               System.out.println("Coordinate addition: "+ ((t2-t1+500000)/1000000) + " ms");
-               
-       }
-       
-}
index 16e338c4a7692597505dc76287a800d986d0d4a1..2d59f7768d9a85f4b35996c88fdf82a7aa5e8c84 100644 (file)
@@ -38,7 +38,7 @@ import net.sf.openrocket.rocketcomponent.RecoveryDevice;
 import net.sf.openrocket.rocketcomponent.Rocket;
 import net.sf.openrocket.rocketcomponent.RocketComponent;
 import net.sf.openrocket.simulation.FlightDataType;
-import net.sf.openrocket.simulation.GUISimulationConditions;
+import net.sf.openrocket.simulation.SimulationOptions;
 import net.sf.openrocket.simulation.RK4SimulationStepper;
 import net.sf.openrocket.startup.Application;
 import net.sf.openrocket.unit.UnitGroup;
@@ -758,7 +758,7 @@ public class Prefs {
        
        public static Simulation getBackgroundSimulation(Rocket rocket) {
                Simulation s = new Simulation(rocket);
-               GUISimulationConditions cond = s.getConditions();
+               SimulationOptions cond = s.getOptions();
                
                cond.setTimeStep(RK4SimulationStepper.RECOMMENDED_TIME_STEP * 2);
                cond.setWindSpeedAverage(1.0);
index f51eadd25b3e26453016a0baafd9bd6e1430eec9..5f2776626c26fcd0764be7a33a8e8fee717f72cd 100644 (file)
@@ -216,7 +216,7 @@ public class Quaternion {
         */
        public double norm() {
                if (norm < 0) {
-                       norm = Math.sqrt(x * x + y * y + z * z + w * w);
+                       norm = MathUtil.safeSqrt(x * x + y * y + z * z + w * w);
                }
                return norm;
        }
index 0205eddfd32bf61c71f5321684b20efdc8795581..48cc648bd0a1e96f1c319aa2c5bc91167313a1a4 100644 (file)
@@ -5,6 +5,7 @@ import net.sf.openrocket.document.Simulation;
 import net.sf.openrocket.optimization.general.OptimizationException;
 import net.sf.openrocket.optimization.general.Point;
 import net.sf.openrocket.rocketcomponent.Rocket;
+import net.sf.openrocket.unit.Unit;
 import net.sf.openrocket.unit.UnitGroup;
 import net.sf.openrocket.unit.Value;
 import net.sf.openrocket.util.Pair;
@@ -43,8 +44,9 @@ public class TestRocketOptimizationFunction {
                final double p1 = 0.4;
                final double p2 = 0.7;
                final double ddist = -0.43;
-               final double dref = 0.33;
+               final Value dref = new Value(ddist, Unit.NOUNIT2);
                final double pvalue = 9.81;
+               final Value pvalueValue = new Value(9.81, Unit.NOUNIT2);
                final double gvalue = 8.81;
                final Point point = new Point(p1, p2);
                
@@ -52,8 +54,9 @@ public class TestRocketOptimizationFunction {
                context.checking(new Expectations() {{
                                oneOf(modifier1).modify(simulation, p1);
                                oneOf(modifier2).modify(simulation, p2);
-                               oneOf(domain).getDistanceToDomain(simulation); will(returnValue(new Pair<Double,Double>(ddist, dref)));
+                               oneOf(domain).getDistanceToDomain(simulation); will(returnValue(new Pair<Double,Value>(ddist, dref)));
                                oneOf(parameter).computeValue(simulation); will(returnValue(pvalue));
+                               oneOf(parameter).getUnitGroup(); will(returnValue(UnitGroup.UNITS_NONE));
                                oneOf(goal).getMinimizationParameter(pvalue); will(returnValue(gvalue));
                                oneOf(modifier1).getCurrentSIValue(simulation); will(returnValue(0.2));
                                oneOf(modifier1).getUnitGroup(); will(returnValue(UnitGroup.UNITS_LENGTH));
@@ -62,7 +65,7 @@ public class TestRocketOptimizationFunction {
                                oneOf(listener).evaluated(point, new Value[] {
                                                new Value(0.2, UnitGroup.UNITS_LENGTH.getDefaultUnit()),
                                                new Value(0.3, UnitGroup.UNITS_LENGTH.getDefaultUnit())
-                               }, dref, pvalue, gvalue);
+                               }, dref, pvalueValue, gvalue);
                }});
                // @formatter:on
                
@@ -87,15 +90,16 @@ public class TestRocketOptimizationFunction {
                final double p1 = 0.4;
                final double p2 = 0.7;
                final double ddist = -0.43;
-               final double dref = 0.33;
+               final Value dref = new Value(0.33, Unit.NOUNIT2);
                final double pvalue = 9.81;
                
                // @formatter:off
                context.checking(new Expectations() {{
                                oneOf(modifier1).modify(simulation, p1);
                                oneOf(modifier2).modify(simulation, p2);
-                               oneOf(domain).getDistanceToDomain(simulation); will(returnValue(new Pair<Double,Double>(ddist, dref)));
+                               oneOf(domain).getDistanceToDomain(simulation); will(returnValue(new Pair<Double,Value>(ddist, dref)));
                                oneOf(parameter).computeValue(simulation); will(returnValue(pvalue));
+                               oneOf(parameter).getUnitGroup(); will(returnValue(UnitGroup.UNITS_NONE));
                                oneOf(goal).getMinimizationParameter(pvalue); will(returnValue(Double.NaN));
                }});
                // @formatter:on
@@ -123,14 +127,14 @@ public class TestRocketOptimizationFunction {
                final double p1 = 0.4;
                final double p2 = 0.7;
                final double ddist = 0.98;
-               final double dref = 0.33;
+               final Value dref = new Value(ddist, Unit.NOUNIT2);
                final Point point = new Point(p1, p2);
                
                // @formatter:off
                context.checking(new Expectations() {{
                                oneOf(modifier1).modify(simulation, p1);
                                oneOf(modifier2).modify(simulation, p2);
-                               oneOf(domain).getDistanceToDomain(simulation); will(returnValue(new Pair<Double,Double>(ddist, dref)));
+                               oneOf(domain).getDistanceToDomain(simulation); will(returnValue(new Pair<Double,Value>(ddist, dref)));
                                oneOf(modifier1).getCurrentSIValue(simulation); will(returnValue(0.2));
                                oneOf(modifier1).getUnitGroup(); will(returnValue(UnitGroup.UNITS_LENGTH));
                                oneOf(modifier2).getCurrentSIValue(simulation); will(returnValue(0.3));
@@ -138,7 +142,7 @@ public class TestRocketOptimizationFunction {
                                oneOf(listener).evaluated(point, new Value[] {
                                                new Value(0.2, UnitGroup.UNITS_LENGTH.getDefaultUnit()),
                                                new Value(0.3, UnitGroup.UNITS_LENGTH.getDefaultUnit())
-                               }, dref, Double.NaN, 1.98E200);
+                               }, dref, null, 1.98E200);
                }});
                // @formatter:on
                
@@ -164,13 +168,13 @@ public class TestRocketOptimizationFunction {
                final double p1 = 0.4;
                final double p2 = 0.7;
                final double ddist = Double.NaN;
-               final double dref = 0.33;
+               final Value dref = new Value(0.33, Unit.NOUNIT2);
                
                // @formatter:off
                context.checking(new Expectations() {{
                                oneOf(modifier1).modify(simulation, p1);
                                oneOf(modifier2).modify(simulation, p2);
-                               oneOf(domain).getDistanceToDomain(simulation); will(returnValue(new Pair<Double,Double>(ddist, dref)));
+                               oneOf(domain).getDistanceToDomain(simulation); will(returnValue(new Pair<Double,Value>(ddist, dref)));
                }});
                // @formatter:on
                
index 6bdcb96eb88812765bb03744611a1d2df566c7a5..deacacf45149e580f01d81dcc52bcc78a1cbf472 100644 (file)
@@ -22,7 +22,9 @@ public class TestGenericModifier {
                value = new TestValue();
                sim = new Simulation(new Rocket());
                
-               gm = new GenericModifier<TestGenericModifier.TestValue>("Test modifier", null,
+               Object related = new Object();
+               
+               gm = new GenericModifier<TestGenericModifier.TestValue>("Test modifier", "Description", related,
                                UnitGroup.UNITS_NONE, 2.0, TestValue.class, "value") {
                        @Override
                        protected TestValue getModifiedObject(Simulation simulation) {
index 0c8c61ff456a6b460245bdafab67923c776cc1ba..9440c4d07d9589512f49dffe2f4b81bd0c7d2cf2 100644 (file)
@@ -5,7 +5,7 @@ import static org.junit.Assert.*;
 import org.junit.Test;
 
 public class ValueTest {
-
+       
        @Test
        public void testValues() {
                Value v1, v2;
@@ -18,21 +18,21 @@ public class ValueTest {
                assertTrue(v1.compareTo(v1) == 0);
                assertTrue(v2.compareTo(v2) == 0);
                
-               v2.setUnit(UnitGroup.UNITS_TEMPERATURE.findApproximate("K"));
+               v2 = new Value(283.15, UnitGroup.UNITS_TEMPERATURE.findApproximate("K"));
                assertTrue(v1.compareTo(v2) > 0);
                assertTrue(v2.compareTo(v1) < 0);
                assertEquals("283 K", v2.toString());
-
-               v2.setUnit(UnitGroup.UNITS_TEMPERATURE.findApproximate("F"));
+               
+               v2 = new Value(283.15, UnitGroup.UNITS_TEMPERATURE.findApproximate("F"));
                assertTrue(v1.compareTo(v2) < 0);
                assertTrue(v2.compareTo(v1) > 0);
                
-               
-               v1.setValue(Double.NaN);
+
+               v1 = new Value(Double.NaN, UnitGroup.UNITS_TEMPERATURE.findApproximate("F"));
                assertTrue(v1.compareTo(v2) > 0);
                assertTrue(v2.compareTo(v1) < 0);
                
-               v2.setValue(Double.NaN);
+               v2 = new Value(Double.NaN, UnitGroup.UNITS_TEMPERATURE.findApproximate("F"));
                assertTrue(v1.compareTo(v2) == 0);
                assertTrue(v1.compareTo(v2) == 0);
                assertEquals("N/A", v1.toString());
index 3b7385f00aa5d9690cb40044e1a15d589e893f94..8ac8319d0aa6b998702aa0cd98fa13dd022f9d64 100644 (file)
@@ -10,153 +10,153 @@ import org.junit.Test;
 public class TextUtilTest {
        
        @Test
-       public void textHexString() {
+       public void testHexString() {
                assertEquals("", TextUtil.hexString(new byte[0]));
                assertEquals("00", TextUtil.hexString(new byte[] { 0x00 }));
                assertEquals("ff", TextUtil.hexString(new byte[] { (byte) 0xff }));
-
-               for (int i=0; i <= 0xff; i++) {
+               
+               for (int i = 0; i <= 0xff; i++) {
                        assertEquals(String.format("%02x", i), TextUtil.hexString(new byte[] { (byte) i }));
                }
                
-               assertEquals("0f1e2d3c4b5a6978", TextUtil.hexString(new byte[] { 
+               assertEquals("0f1e2d3c4b5a6978", TextUtil.hexString(new byte[] {
                                0x0f, 0x1e, 0x2d, 0x3c, 0x4b, 0x5a, 0x69, 0x78
                }));
                
                Random rnd = new Random();
-               for (int count=0; count<10; count++) {
+               for (int count = 0; count < 10; count++) {
                        int n = rnd.nextInt(100);
                        byte[] bytes = new byte[n];
                        rnd.nextBytes(bytes);
                        StringBuilder sb = new StringBuilder();
-                       for (byte b: bytes) {
+                       for (byte b : bytes) {
                                sb.append(String.format("%02x", b & 0xFF));
                        }
                        assertEquals(sb.toString(), TextUtil.hexString(bytes));
                }
        }
-
+       
        @Test
        public void specialCaseTest() {
-               assertEquals("NaN",TextUtil.doubleToString(Double.NaN));
-               assertEquals("Inf",TextUtil.doubleToString(Double.POSITIVE_INFINITY));
-               assertEquals("-Inf",TextUtil.doubleToString(Double.NEGATIVE_INFINITY));
-               assertEquals("0",TextUtil.doubleToString(0.0));
-               assertEquals("0",TextUtil.doubleToString(MathUtil.EPSILON/3));
-               assertEquals("0",TextUtil.doubleToString(-MathUtil.EPSILON/3));
+               assertEquals("NaN", TextUtil.doubleToString(Double.NaN));
+               assertEquals("Inf", TextUtil.doubleToString(Double.POSITIVE_INFINITY));
+               assertEquals("-Inf", TextUtil.doubleToString(Double.NEGATIVE_INFINITY));
+               assertEquals("0", TextUtil.doubleToString(0.0));
+               assertEquals("0", TextUtil.doubleToString(MathUtil.EPSILON / 3));
+               assertEquals("0", TextUtil.doubleToString(-MathUtil.EPSILON / 3));
        }
        
        @Test
        public void longTest() {
                
-               assertEquals("3.1416e-5", TextUtil.doubleToString(PI*1e-5));
-               assertEquals("3.1416e-4", TextUtil.doubleToString(PI*1e-4));
-               assertEquals("0.0031416", TextUtil.doubleToString(PI*1e-3));
-               assertEquals("0.031416",  TextUtil.doubleToString(PI*1e-2));
-               assertEquals("0.31416",   TextUtil.doubleToString(PI*1e-1));
-               assertEquals("3.1416",    TextUtil.doubleToString(PI));
-               assertEquals("31.416",    TextUtil.doubleToString(PI*1e1));
-               assertEquals("314.16",    TextUtil.doubleToString(PI*1e2));
-               assertEquals("3141.6",    TextUtil.doubleToString(PI*1e3));
-               assertEquals("31416",     TextUtil.doubleToString(PI*1e4));
-               assertEquals("314159",    TextUtil.doubleToString(PI*1e5));
-               assertEquals("3141593",   TextUtil.doubleToString(PI*1e6));
-               assertEquals("31415927",  TextUtil.doubleToString(PI*1e7));
-               assertEquals("3.1416e8",  TextUtil.doubleToString(PI*1e8));
-               assertEquals("3.1416e9",  TextUtil.doubleToString(PI*1e9));
-               assertEquals("3.1416e10", TextUtil.doubleToString(PI*1e10));
-
-               assertEquals("-3.1416e-5", TextUtil.doubleToString(-PI*1e-5));
-               assertEquals("-3.1416e-4", TextUtil.doubleToString(-PI*1e-4));
-               assertEquals("-0.0031416", TextUtil.doubleToString(-PI*1e-3));
-               assertEquals("-0.031416",  TextUtil.doubleToString(-PI*1e-2));
-               assertEquals("-0.31416",   TextUtil.doubleToString(-PI*1e-1));
-               assertEquals("-3.1416",    TextUtil.doubleToString(-PI));
-               assertEquals("-31.416",    TextUtil.doubleToString(-PI*1e1));
-               assertEquals("-314.16",    TextUtil.doubleToString(-PI*1e2));
-               assertEquals("-3141.6",    TextUtil.doubleToString(-PI*1e3));
-               assertEquals("-31416",     TextUtil.doubleToString(-PI*1e4));
-               assertEquals("-314159",    TextUtil.doubleToString(-PI*1e5));
-               assertEquals("-3141593",   TextUtil.doubleToString(-PI*1e6));
-               assertEquals("-31415927",  TextUtil.doubleToString(-PI*1e7));
-               assertEquals("-3.1416e8",  TextUtil.doubleToString(-PI*1e8));
-               assertEquals("-3.1416e9",  TextUtil.doubleToString(-PI*1e9));
-               assertEquals("-3.1416e10", TextUtil.doubleToString(-PI*1e10));
-
+               assertEquals("3.1416e-5", TextUtil.doubleToString(PI * 1e-5));
+               assertEquals("3.1416e-4", TextUtil.doubleToString(PI * 1e-4));
+               assertEquals("0.0031416", TextUtil.doubleToString(PI * 1e-3));
+               assertEquals("0.031416", TextUtil.doubleToString(PI * 1e-2));
+               assertEquals("0.31416", TextUtil.doubleToString(PI * 1e-1));
+               assertEquals("3.1416", TextUtil.doubleToString(PI));
+               assertEquals("31.416", TextUtil.doubleToString(PI * 1e1));
+               assertEquals("314.16", TextUtil.doubleToString(PI * 1e2));
+               assertEquals("3141.6", TextUtil.doubleToString(PI * 1e3));
+               assertEquals("31416", TextUtil.doubleToString(PI * 1e4));
+               assertEquals("314159", TextUtil.doubleToString(PI * 1e5));
+               assertEquals("3141593", TextUtil.doubleToString(PI * 1e6));
+               assertEquals("31415927", TextUtil.doubleToString(PI * 1e7));
+               assertEquals("3.1416e8", TextUtil.doubleToString(PI * 1e8));
+               assertEquals("3.1416e9", TextUtil.doubleToString(PI * 1e9));
+               assertEquals("3.1416e10", TextUtil.doubleToString(PI * 1e10));
+               
+               assertEquals("-3.1416e-5", TextUtil.doubleToString(-PI * 1e-5));
+               assertEquals("-3.1416e-4", TextUtil.doubleToString(-PI * 1e-4));
+               assertEquals("-0.0031416", TextUtil.doubleToString(-PI * 1e-3));
+               assertEquals("-0.031416", TextUtil.doubleToString(-PI * 1e-2));
+               assertEquals("-0.31416", TextUtil.doubleToString(-PI * 1e-1));
+               assertEquals("-3.1416", TextUtil.doubleToString(-PI));
+               assertEquals("-31.416", TextUtil.doubleToString(-PI * 1e1));
+               assertEquals("-314.16", TextUtil.doubleToString(-PI * 1e2));
+               assertEquals("-3141.6", TextUtil.doubleToString(-PI * 1e3));
+               assertEquals("-31416", TextUtil.doubleToString(-PI * 1e4));
+               assertEquals("-314159", TextUtil.doubleToString(-PI * 1e5));
+               assertEquals("-3141593", TextUtil.doubleToString(-PI * 1e6));
+               assertEquals("-31415927", TextUtil.doubleToString(-PI * 1e7));
+               assertEquals("-3.1416e8", TextUtil.doubleToString(-PI * 1e8));
+               assertEquals("-3.1416e9", TextUtil.doubleToString(-PI * 1e9));
+               assertEquals("-3.1416e10", TextUtil.doubleToString(-PI * 1e10));
+               
        }
        
        @Test
        public void shortTest() {
                double p = 3.1;
-               assertEquals("3.1e-5", TextUtil.doubleToString(p*1e-5));
-               assertEquals("3.1e-4", TextUtil.doubleToString(p*1e-4));
-               assertEquals("0.0031", TextUtil.doubleToString(p*1e-3));
-               assertEquals("0.031",  TextUtil.doubleToString(p*1e-2));
-               assertEquals("0.31",   TextUtil.doubleToString(p*1e-1));
-               assertEquals("3.1",    TextUtil.doubleToString(p));
-               assertEquals("31",     TextUtil.doubleToString(p*1e1));
-               assertEquals("310",    TextUtil.doubleToString(p*1e2));
-               assertEquals("3100",   TextUtil.doubleToString(p*1e3));
-               assertEquals("31000",  TextUtil.doubleToString(p*1e4));
-               assertEquals("3.1e5",  TextUtil.doubleToString(p*1e5));
-               assertEquals("3.1e6",  TextUtil.doubleToString(p*1e6));
-               assertEquals("3.1e7",  TextUtil.doubleToString(p*1e7));
-               assertEquals("3.1e8",  TextUtil.doubleToString(p*1e8));
-               assertEquals("3.1e9",  TextUtil.doubleToString(p*1e9));
-               assertEquals("3.1e10", TextUtil.doubleToString(p*1e10));
-
-               assertEquals("-3.1e-5", TextUtil.doubleToString(-p*1e-5));
-               assertEquals("-3.1e-4", TextUtil.doubleToString(-p*1e-4));
-               assertEquals("-0.0031", TextUtil.doubleToString(-p*1e-3));
-               assertEquals("-0.031",  TextUtil.doubleToString(-p*1e-2));
-               assertEquals("-0.31",   TextUtil.doubleToString(-p*1e-1));
-               assertEquals("-3.1",    TextUtil.doubleToString(-p));
-               assertEquals("-31",     TextUtil.doubleToString(-p*1e1));
-               assertEquals("-310",    TextUtil.doubleToString(-p*1e2));
-               assertEquals("-3100",   TextUtil.doubleToString(-p*1e3));
-               assertEquals("-31000",  TextUtil.doubleToString(-p*1e4));
-               assertEquals("-3.1e5",  TextUtil.doubleToString(-p*1e5));
-               assertEquals("-3.1e6",  TextUtil.doubleToString(-p*1e6));
-               assertEquals("-3.1e7",  TextUtil.doubleToString(-p*1e7));
-               assertEquals("-3.1e8",  TextUtil.doubleToString(-p*1e8));
-               assertEquals("-3.1e9",  TextUtil.doubleToString(-p*1e9));
-               assertEquals("-3.1e10", TextUtil.doubleToString(-p*1e10));
-
+               assertEquals("3.1e-5", TextUtil.doubleToString(p * 1e-5));
+               assertEquals("3.1e-4", TextUtil.doubleToString(p * 1e-4));
+               assertEquals("0.0031", TextUtil.doubleToString(p * 1e-3));
+               assertEquals("0.031", TextUtil.doubleToString(p * 1e-2));
+               assertEquals("0.31", TextUtil.doubleToString(p * 1e-1));
+               assertEquals("3.1", TextUtil.doubleToString(p));
+               assertEquals("31", TextUtil.doubleToString(p * 1e1));
+               assertEquals("310", TextUtil.doubleToString(p * 1e2));
+               assertEquals("3100", TextUtil.doubleToString(p * 1e3));
+               assertEquals("31000", TextUtil.doubleToString(p * 1e4));
+               assertEquals("3.1e5", TextUtil.doubleToString(p * 1e5));
+               assertEquals("3.1e6", TextUtil.doubleToString(p * 1e6));
+               assertEquals("3.1e7", TextUtil.doubleToString(p * 1e7));
+               assertEquals("3.1e8", TextUtil.doubleToString(p * 1e8));
+               assertEquals("3.1e9", TextUtil.doubleToString(p * 1e9));
+               assertEquals("3.1e10", TextUtil.doubleToString(p * 1e10));
+               
+               assertEquals("-3.1e-5", TextUtil.doubleToString(-p * 1e-5));
+               assertEquals("-3.1e-4", TextUtil.doubleToString(-p * 1e-4));
+               assertEquals("-0.0031", TextUtil.doubleToString(-p * 1e-3));
+               assertEquals("-0.031", TextUtil.doubleToString(-p * 1e-2));
+               assertEquals("-0.31", TextUtil.doubleToString(-p * 1e-1));
+               assertEquals("-3.1", TextUtil.doubleToString(-p));
+               assertEquals("-31", TextUtil.doubleToString(-p * 1e1));
+               assertEquals("-310", TextUtil.doubleToString(-p * 1e2));
+               assertEquals("-3100", TextUtil.doubleToString(-p * 1e3));
+               assertEquals("-31000", TextUtil.doubleToString(-p * 1e4));
+               assertEquals("-3.1e5", TextUtil.doubleToString(-p * 1e5));
+               assertEquals("-3.1e6", TextUtil.doubleToString(-p * 1e6));
+               assertEquals("-3.1e7", TextUtil.doubleToString(-p * 1e7));
+               assertEquals("-3.1e8", TextUtil.doubleToString(-p * 1e8));
+               assertEquals("-3.1e9", TextUtil.doubleToString(-p * 1e9));
+               assertEquals("-3.1e10", TextUtil.doubleToString(-p * 1e10));
+               
                p = 3;
-               assertEquals("3e-5", TextUtil.doubleToString(p*1e-5));
-               assertEquals("3e-4", TextUtil.doubleToString(p*1e-4));
-               assertEquals("3e-3", TextUtil.doubleToString(p*1e-3));
-               assertEquals("0.03", TextUtil.doubleToString(p*1e-2));
-               assertEquals("0.3",  TextUtil.doubleToString(p*1e-1));
-               assertEquals("3",    TextUtil.doubleToString(p));
-               assertEquals("30",   TextUtil.doubleToString(p*1e1));
-               assertEquals("300",  TextUtil.doubleToString(p*1e2));
-               assertEquals("3e3",  TextUtil.doubleToString(p*1e3));
-               assertEquals("3e4",  TextUtil.doubleToString(p*1e4));
-               assertEquals("3e5",  TextUtil.doubleToString(p*1e5));
-               assertEquals("3e6",  TextUtil.doubleToString(p*1e6));
-               assertEquals("3e7",  TextUtil.doubleToString(p*1e7));
-               assertEquals("3e8",  TextUtil.doubleToString(p*1e8));
-               assertEquals("3e9",  TextUtil.doubleToString(p*1e9));
-               assertEquals("3e10", TextUtil.doubleToString(p*1e10));
-
-               assertEquals("-3e-5", TextUtil.doubleToString(-p*1e-5));
-               assertEquals("-3e-4", TextUtil.doubleToString(-p*1e-4));
-               assertEquals("-3e-3", TextUtil.doubleToString(-p*1e-3));
-               assertEquals("-0.03", TextUtil.doubleToString(-p*1e-2));
-               assertEquals("-0.3",  TextUtil.doubleToString(-p*1e-1));
-               assertEquals("-3",    TextUtil.doubleToString(-p));
-               assertEquals("-30",   TextUtil.doubleToString(-p*1e1));
-               assertEquals("-300",  TextUtil.doubleToString(-p*1e2));
-               assertEquals("-3e3",  TextUtil.doubleToString(-p*1e3));
-               assertEquals("-3e4",  TextUtil.doubleToString(-p*1e4));
-               assertEquals("-3e5",  TextUtil.doubleToString(-p*1e5));
-               assertEquals("-3e6",  TextUtil.doubleToString(-p*1e6));
-               assertEquals("-3e7",  TextUtil.doubleToString(-p*1e7));
-               assertEquals("-3e8",  TextUtil.doubleToString(-p*1e8));
-               assertEquals("-3e9",  TextUtil.doubleToString(-p*1e9));
-               assertEquals("-3e10", TextUtil.doubleToString(-p*1e10));
-
+               assertEquals("3e-5", TextUtil.doubleToString(p * 1e-5));
+               assertEquals("3e-4", TextUtil.doubleToString(p * 1e-4));
+               assertEquals("3e-3", TextUtil.doubleToString(p * 1e-3));
+               assertEquals("0.03", TextUtil.doubleToString(p * 1e-2));
+               assertEquals("0.3", TextUtil.doubleToString(p * 1e-1));
+               assertEquals("3", TextUtil.doubleToString(p));
+               assertEquals("30", TextUtil.doubleToString(p * 1e1));
+               assertEquals("300", TextUtil.doubleToString(p * 1e2));
+               assertEquals("3e3", TextUtil.doubleToString(p * 1e3));
+               assertEquals("3e4", TextUtil.doubleToString(p * 1e4));
+               assertEquals("3e5", TextUtil.doubleToString(p * 1e5));
+               assertEquals("3e6", TextUtil.doubleToString(p * 1e6));
+               assertEquals("3e7", TextUtil.doubleToString(p * 1e7));
+               assertEquals("3e8", TextUtil.doubleToString(p * 1e8));
+               assertEquals("3e9", TextUtil.doubleToString(p * 1e9));
+               assertEquals("3e10", TextUtil.doubleToString(p * 1e10));
+               
+               assertEquals("-3e-5", TextUtil.doubleToString(-p * 1e-5));
+               assertEquals("-3e-4", TextUtil.doubleToString(-p * 1e-4));
+               assertEquals("-3e-3", TextUtil.doubleToString(-p * 1e-3));
+               assertEquals("-0.03", TextUtil.doubleToString(-p * 1e-2));
+               assertEquals("-0.3", TextUtil.doubleToString(-p * 1e-1));
+               assertEquals("-3", TextUtil.doubleToString(-p));
+               assertEquals("-30", TextUtil.doubleToString(-p * 1e1));
+               assertEquals("-300", TextUtil.doubleToString(-p * 1e2));
+               assertEquals("-3e3", TextUtil.doubleToString(-p * 1e3));
+               assertEquals("-3e4", TextUtil.doubleToString(-p * 1e4));
+               assertEquals("-3e5", TextUtil.doubleToString(-p * 1e5));
+               assertEquals("-3e6", TextUtil.doubleToString(-p * 1e6));
+               assertEquals("-3e7", TextUtil.doubleToString(-p * 1e7));
+               assertEquals("-3e8", TextUtil.doubleToString(-p * 1e8));
+               assertEquals("-3e9", TextUtil.doubleToString(-p * 1e9));
+               assertEquals("-3e10", TextUtil.doubleToString(-p * 1e10));
+               
        }
        
        @Test
@@ -164,42 +164,42 @@ public class TextUtilTest {
                
                assertEquals("1.001", TextUtil.doubleToString(1.00096));
                
-               
+
                /*
                 * Not testing with 1.00015 because it might be changed during number formatting
                 * calculations.  Its rounding is basically arbitrary anyway.
                 */
-               
+
                assertEquals("1.0002e-5", TextUtil.doubleToString(1.0001500001e-5));
                assertEquals("1.0001e-5", TextUtil.doubleToString(1.0001499999e-5));
                assertEquals("1.0002e-4", TextUtil.doubleToString(1.0001500001e-4));
                assertEquals("1.0001e-4", TextUtil.doubleToString(1.0001499999e-4));
                assertEquals("0.0010002", TextUtil.doubleToString(1.0001500001e-3));
                assertEquals("0.0010001", TextUtil.doubleToString(1.0001499999e-3));
-               assertEquals("0.010002",  TextUtil.doubleToString(1.0001500001e-2));
-               assertEquals("0.010001",  TextUtil.doubleToString(1.0001499999e-2));
-               assertEquals("0.10002",   TextUtil.doubleToString(1.0001500001e-1));
-               assertEquals("0.10001",   TextUtil.doubleToString(1.0001499999e-1));
-               assertEquals("1.0002",    TextUtil.doubleToString(1.0001500001));
-               assertEquals("1.0001",    TextUtil.doubleToString(1.0001499999));
-               assertEquals("10.002",    TextUtil.doubleToString(1.0001500001e1));
-               assertEquals("10.001",    TextUtil.doubleToString(1.0001499999e1));
-               assertEquals("100.02",    TextUtil.doubleToString(1.0001500001e2));
-               assertEquals("100.01",    TextUtil.doubleToString(1.0001499999e2));
-               assertEquals("1000.2",    TextUtil.doubleToString(1.0001500001e3));
-               assertEquals("1000.1",    TextUtil.doubleToString(1.0001499999e3));
-               assertEquals("10002",     TextUtil.doubleToString(1.0001500001e4));
-               assertEquals("10001",     TextUtil.doubleToString(1.0001499999e4));
-               assertEquals("100012",    TextUtil.doubleToString(1.00011500001e5));
-               assertEquals("100011",    TextUtil.doubleToString(1.00011499999e5));
-               assertEquals("1000112",   TextUtil.doubleToString(1.000111500001e6));
-               assertEquals("1000111",   TextUtil.doubleToString(1.000111499999e6));
-               assertEquals("10001112",  TextUtil.doubleToString(1.0001111500001e7));
-               assertEquals("10001111",  TextUtil.doubleToString(1.0001111499999e7));
-               assertEquals("1.0002e8",  TextUtil.doubleToString(1.0001500001e8));
-               assertEquals("1.0001e8",  TextUtil.doubleToString(1.0001499999e8));
-               assertEquals("1.0002e9",  TextUtil.doubleToString(1.0001500001e9));
-               assertEquals("1.0001e9",  TextUtil.doubleToString(1.0001499999e9));
+               assertEquals("0.010002", TextUtil.doubleToString(1.0001500001e-2));
+               assertEquals("0.010001", TextUtil.doubleToString(1.0001499999e-2));
+               assertEquals("0.10002", TextUtil.doubleToString(1.0001500001e-1));
+               assertEquals("0.10001", TextUtil.doubleToString(1.0001499999e-1));
+               assertEquals("1.0002", TextUtil.doubleToString(1.0001500001));
+               assertEquals("1.0001", TextUtil.doubleToString(1.0001499999));
+               assertEquals("10.002", TextUtil.doubleToString(1.0001500001e1));
+               assertEquals("10.001", TextUtil.doubleToString(1.0001499999e1));
+               assertEquals("100.02", TextUtil.doubleToString(1.0001500001e2));
+               assertEquals("100.01", TextUtil.doubleToString(1.0001499999e2));
+               assertEquals("1000.2", TextUtil.doubleToString(1.0001500001e3));
+               assertEquals("1000.1", TextUtil.doubleToString(1.0001499999e3));
+               assertEquals("10002", TextUtil.doubleToString(1.0001500001e4));
+               assertEquals("10001", TextUtil.doubleToString(1.0001499999e4));
+               assertEquals("100012", TextUtil.doubleToString(1.00011500001e5));
+               assertEquals("100011", TextUtil.doubleToString(1.00011499999e5));
+               assertEquals("1000112", TextUtil.doubleToString(1.000111500001e6));
+               assertEquals("1000111", TextUtil.doubleToString(1.000111499999e6));
+               assertEquals("10001112", TextUtil.doubleToString(1.0001111500001e7));
+               assertEquals("10001111", TextUtil.doubleToString(1.0001111499999e7));
+               assertEquals("1.0002e8", TextUtil.doubleToString(1.0001500001e8));
+               assertEquals("1.0001e8", TextUtil.doubleToString(1.0001499999e8));
+               assertEquals("1.0002e9", TextUtil.doubleToString(1.0001500001e9));
+               assertEquals("1.0001e9", TextUtil.doubleToString(1.0001499999e9));
                assertEquals("1.0002e10", TextUtil.doubleToString(1.0001500001e10));
                assertEquals("1.0001e10", TextUtil.doubleToString(1.0001499999e10));
                
@@ -210,30 +210,30 @@ public class TextUtilTest {
                assertEquals("-1.0001e-4", TextUtil.doubleToString(-1.0001499999e-4));
                assertEquals("-0.0010002", TextUtil.doubleToString(-1.0001500001e-3));
                assertEquals("-0.0010001", TextUtil.doubleToString(-1.0001499999e-3));
-               assertEquals("-0.010002",  TextUtil.doubleToString(-1.0001500001e-2));
-               assertEquals("-0.010001",  TextUtil.doubleToString(-1.0001499999e-2));
-               assertEquals("-0.10002",   TextUtil.doubleToString(-1.0001500001e-1));
-               assertEquals("-0.10001",   TextUtil.doubleToString(-1.0001499999e-1));
-               assertEquals("-1.0002",    TextUtil.doubleToString(-1.0001500001));
-               assertEquals("-1.0001",    TextUtil.doubleToString(-1.0001499999));
-               assertEquals("-10.002",    TextUtil.doubleToString(-1.0001500001e1));
-               assertEquals("-10.001",    TextUtil.doubleToString(-1.0001499999e1));
-               assertEquals("-100.02",    TextUtil.doubleToString(-1.0001500001e2));
-               assertEquals("-100.01",    TextUtil.doubleToString(-1.0001499999e2));
-               assertEquals("-1000.2",    TextUtil.doubleToString(-1.0001500001e3));
-               assertEquals("-1000.1",    TextUtil.doubleToString(-1.0001499999e3));
-               assertEquals("-10002",     TextUtil.doubleToString(-1.0001500001e4));
-               assertEquals("-10001",     TextUtil.doubleToString(-1.0001499999e4));
-               assertEquals("-100012",    TextUtil.doubleToString(-1.00011500001e5));
-               assertEquals("-100011",    TextUtil.doubleToString(-1.00011499999e5));
-               assertEquals("-1000112",   TextUtil.doubleToString(-1.000111500001e6));
-               assertEquals("-1000111",   TextUtil.doubleToString(-1.000111499999e6));
-               assertEquals("-10001112",  TextUtil.doubleToString(-1.0001111500001e7));
-               assertEquals("-10001111",  TextUtil.doubleToString(-1.0001111499999e7));
-               assertEquals("-1.0002e8",  TextUtil.doubleToString(-1.0001500001e8));
-               assertEquals("-1.0001e8",  TextUtil.doubleToString(-1.0001499999e8));
-               assertEquals("-1.0002e9",  TextUtil.doubleToString(-1.0001500001e9));
-               assertEquals("-1.0001e9",  TextUtil.doubleToString(-1.0001499999e9));
+               assertEquals("-0.010002", TextUtil.doubleToString(-1.0001500001e-2));
+               assertEquals("-0.010001", TextUtil.doubleToString(-1.0001499999e-2));
+               assertEquals("-0.10002", TextUtil.doubleToString(-1.0001500001e-1));
+               assertEquals("-0.10001", TextUtil.doubleToString(-1.0001499999e-1));
+               assertEquals("-1.0002", TextUtil.doubleToString(-1.0001500001));
+               assertEquals("-1.0001", TextUtil.doubleToString(-1.0001499999));
+               assertEquals("-10.002", TextUtil.doubleToString(-1.0001500001e1));
+               assertEquals("-10.001", TextUtil.doubleToString(-1.0001499999e1));
+               assertEquals("-100.02", TextUtil.doubleToString(-1.0001500001e2));
+               assertEquals("-100.01", TextUtil.doubleToString(-1.0001499999e2));
+               assertEquals("-1000.2", TextUtil.doubleToString(-1.0001500001e3));
+               assertEquals("-1000.1", TextUtil.doubleToString(-1.0001499999e3));
+               assertEquals("-10002", TextUtil.doubleToString(-1.0001500001e4));
+               assertEquals("-10001", TextUtil.doubleToString(-1.0001499999e4));
+               assertEquals("-100012", TextUtil.doubleToString(-1.00011500001e5));
+               assertEquals("-100011", TextUtil.doubleToString(-1.00011499999e5));
+               assertEquals("-1000112", TextUtil.doubleToString(-1.000111500001e6));
+               assertEquals("-1000111", TextUtil.doubleToString(-1.000111499999e6));
+               assertEquals("-10001112", TextUtil.doubleToString(-1.0001111500001e7));
+               assertEquals("-10001111", TextUtil.doubleToString(-1.0001111499999e7));
+               assertEquals("-1.0002e8", TextUtil.doubleToString(-1.0001500001e8));
+               assertEquals("-1.0001e8", TextUtil.doubleToString(-1.0001499999e8));
+               assertEquals("-1.0002e9", TextUtil.doubleToString(-1.0001500001e9));
+               assertEquals("-1.0001e9", TextUtil.doubleToString(-1.0001499999e9));
                assertEquals("-1.0002e10", TextUtil.doubleToString(-1.0001500001e10));
                assertEquals("-1.0001e10", TextUtil.doubleToString(-1.0001499999e10));
                
@@ -242,10 +242,10 @@ public class TextUtilTest {
        
        @Test
        public void randomTest() {
-               for (int i=0; i<10000; i++) {
+               for (int i = 0; i < 10000; i++) {
                        double orig = Math.random();
                        double result;
-                       double expected = Math.rint(orig*100000) / 100000.0;
+                       double expected = Math.rint(orig * 100000) / 100000.0;
                        
                        if (orig < 0.1)
                                continue;
index 2377e85ccc843cef1dade7ff5646a4380538c5bd..ffb4aada581bc9628fe9c270ba84a4916f60a83f 100644 (file)
Binary files a/web/html/techdoc.pdf and b/web/html/techdoc.pdf differ