+ */
+public class CompassSelector extends CompassPointer {
+
+ private final DoubleModel model;
+
+ public CompassSelector(DoubleModel model) {
+ super(model);
+ this.model = model;
+
+ MouseAdapter mouse = new MouseAdapter() {
+ private boolean dragging = false;
+
+ @Override
+ public void mousePressed(MouseEvent e) {
+ if (!isWithinCircle(e))
+ return;
+ if (e.getButton() != MouseEvent.BUTTON1)
+ return;
+ dragging = true;
+ clicked(e);
+ }
+
+ @Override
+ public void mouseReleased(MouseEvent e) {
+ if (e.getButton() != MouseEvent.BUTTON1)
+ return;
+ dragging = false;
+ }
+
+
+ @Override
+ public void mouseDragged(MouseEvent e) {
+ if (!dragging)
+ return;
+ clicked(e);
+ }
+ };
+ this.addMouseListener(mouse);
+ this.addMouseMotionListener(mouse);
+
+ }
+
+ private boolean isWithinCircle(MouseEvent e) {
+ if (mid < 0 || width < 0) {
+ return false;
+ }
+
+ int x = e.getX() - mid;
+ int y = e.getY() - mid;
+
+ double distance = Math.hypot(x, y);
+ return distance < width / 2;
+ }
+
+ private void clicked(MouseEvent e) {
+
+ if (mid < 0 || width < 0) {
+ return;
+ }
+
+ int x = e.getX() - mid;
+ int y = e.getY() - mid;
+
+ double distance = Math.hypot(x, y);
+
+ double theta = Math.atan2(y, x);
+ theta = MathUtil.reduce360(theta + Math.PI / 2);
+
+ // Round the value appropriately
+ theta = Math.toDegrees(theta);
+
+ if (distance > 50) {
+ theta = Math.round(theta);
+ } else if (distance > 10) {
+ theta = 5 * Math.round(theta / 5);
+ } else {
+ // Do nothing if too close to center
+ return;
+ }
+ theta = Math.toRadians(theta);
+
+ model.setValue(theta);
+ }
+
+}
diff --git a/src/net/sf/openrocket/gui/components/compass/Tester.java b/src/net/sf/openrocket/gui/components/compass/Tester.java
new file mode 100644
index 000000000..eda95f567
--- /dev/null
+++ b/src/net/sf/openrocket/gui/components/compass/Tester.java
@@ -0,0 +1,66 @@
+package net.sf.openrocket.gui.components.compass;
+
+import java.awt.Dimension;
+import java.lang.reflect.InvocationTargetException;
+
+import javax.swing.JFrame;
+import javax.swing.JPanel;
+import javax.swing.JSpinner;
+import javax.swing.SwingUtilities;
+
+import net.miginfocom.swing.MigLayout;
+import net.sf.openrocket.gui.adaptors.DoubleModel;
+import net.sf.openrocket.l10n.ResourceBundleTranslator;
+import net.sf.openrocket.startup.Application;
+import net.sf.openrocket.unit.UnitGroup;
+import net.sf.openrocket.util.GUIUtil;
+
+public class Tester {
+
+
+ public static void main(String[] args) throws InterruptedException, InvocationTargetException {
+
+ Application.setBaseTranslator(new ResourceBundleTranslator("l10n.messages"));
+
+ GUIUtil.setBestLAF();
+
+ SwingUtilities.invokeAndWait(new Runnable() {
+ @Override
+ public void run() {
+ JFrame frame = new JFrame();
+
+ JPanel panel = new JPanel(new MigLayout("fill"));
+ DoubleModel model = new DoubleModel(Math.toRadians(45), UnitGroup.UNITS_ANGLE);
+ DoubleModel second = new DoubleModel(Math.toRadians(30), UnitGroup.UNITS_ANGLE);
+
+
+ CompassPointer rose = new CompassSelector(model);
+ rose.setPreferredSize(new Dimension(300, 300));
+ rose.setSecondaryModel(second);
+ panel.add(rose);
+
+ rose = new CompassPointer(model);
+ rose.setPreferredSize(new Dimension(24, 24));
+ panel.add(rose);
+ rose.setMarkerFont(null);
+ rose.setPointerArrow(false);
+ rose.setPointerWidth(0.45f);
+ rose.setScaler(1.0f);
+
+ JSpinner spin = new JSpinner(model.getSpinnerModel());
+ spin.setPreferredSize(new Dimension(50, 20));
+ panel.add(spin, "wrap para");
+
+
+ CompassSelectionButton button = new CompassSelectionButton(model);
+ panel.add(button);
+
+
+ frame.add(panel);
+ frame.pack();
+ frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+ frame.setVisible(true);
+ }
+ });
+ }
+}
diff --git a/src/net/sf/openrocket/gui/configdialog/ComponentConfigDialog.java b/src/net/sf/openrocket/gui/configdialog/ComponentConfigDialog.java
index a04c65b61..0f9f11597 100644
--- a/src/net/sf/openrocket/gui/configdialog/ComponentConfigDialog.java
+++ b/src/net/sf/openrocket/gui/configdialog/ComponentConfigDialog.java
@@ -1,10 +1,7 @@
package net.sf.openrocket.gui.configdialog;
-import java.awt.Point;
import java.awt.Window;
-import java.awt.event.ComponentAdapter;
-import java.awt.event.ComponentEvent;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
@@ -18,7 +15,6 @@ import net.sf.openrocket.rocketcomponent.RocketComponent;
import net.sf.openrocket.startup.Application;
import net.sf.openrocket.util.BugException;
import net.sf.openrocket.util.GUIUtil;
-import net.sf.openrocket.util.Prefs;
import net.sf.openrocket.util.Reflection;
/**
@@ -44,7 +40,7 @@ public class ComponentConfigDialog extends JDialog implements ComponentChangeLis
private final Window parent;
private static final Translator trans = Application.getTranslator();
-
+
private ComponentConfigDialog(Window parent, OpenRocketDocument document,
RocketComponent component) {
super(parent);
@@ -53,22 +49,7 @@ public class ComponentConfigDialog extends JDialog implements ComponentChangeLis
setComponent(document, component);
GUIUtil.setDisposableDialogOptions(this, null);
-
- // Set window position according to preferences, and set prefs when moving
- Point position = Prefs.getWindowPosition(this.getClass());
- if (position != null) {
- this.setLocationByPlatform(false);
- this.setLocation(position);
- }
-
- this.addComponentListener(new ComponentAdapter() {
- @Override
- public void componentMoved(ComponentEvent e) {
- Prefs.setWindowPosition(ComponentConfigDialog.this.getClass(),
- ComponentConfigDialog.this.getLocation());
- }
- });
-
+ GUIUtil.rememberWindowPosition(this);
}
diff --git a/src/net/sf/openrocket/gui/dialogs/optimization/OptimizationPlotDialog.java b/src/net/sf/openrocket/gui/dialogs/optimization/OptimizationPlotDialog.java
index 04885a82c..c90c31ab1 100644
--- a/src/net/sf/openrocket/gui/dialogs/optimization/OptimizationPlotDialog.java
+++ b/src/net/sf/openrocket/gui/dialogs/optimization/OptimizationPlotDialog.java
@@ -113,6 +113,7 @@ public class OptimizationPlotDialog extends JDialog {
this.add(panel);
GUIUtil.setDisposableDialogOptions(this, close);
+ GUIUtil.rememberWindowSize(this);
}
diff --git a/src/net/sf/openrocket/gui/main/BasicFrame.java b/src/net/sf/openrocket/gui/main/BasicFrame.java
index 5fb99fb23..5f2d92e01 100644
--- a/src/net/sf/openrocket/gui/main/BasicFrame.java
+++ b/src/net/sf/openrocket/gui/main/BasicFrame.java
@@ -6,8 +6,6 @@ import java.awt.Toolkit;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
-import java.awt.event.ComponentAdapter;
-import java.awt.event.ComponentEvent;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
@@ -233,19 +231,16 @@ public class BasicFrame extends JFrame {
setTitle();
this.pack();
- Dimension size = Prefs.getWindowSize(this.getClass());
- if (size == null) {
- size = Toolkit.getDefaultToolkit().getScreenSize();
- size.width = size.width * 9 / 10;
- size.height = size.height * 9 / 10;
- }
+
+ // Set initial window size
+ Dimension size = Toolkit.getDefaultToolkit().getScreenSize();
+ size.width = size.width * 9 / 10;
+ size.height = size.height * 9 / 10;
this.setSize(size);
- this.addComponentListener(new ComponentAdapter() {
- @Override
- public void componentResized(ComponentEvent e) {
- Prefs.setWindowSize(BasicFrame.this.getClass(), BasicFrame.this.getSize());
- }
- });
+
+ // Remember changed size
+ GUIUtil.rememberWindowSize(this);
+
this.setLocationByPlatform(true);
GUIUtil.setWindowIcons(this);
@@ -259,8 +254,8 @@ public class BasicFrame extends JFrame {
closeAction();
}
});
- frames.add(this);
+ frames.add(this);
log.debug("BasicFrame instantiation complete");
}
@@ -673,14 +668,13 @@ public class BasicFrame extends JFrame {
menu = new JMenu(trans.get("main.menu.help"));
menu.setMnemonic(KeyEvent.VK_H);
- //// Information about OpenRocket
menu.getAccessibleContext().setAccessibleDescription(trans.get("main.menu.help.desc"));
menubar.add(menu);
//// License
item = new JMenuItem(trans.get("main.menu.help.license"), KeyEvent.VK_L);
- //// OpenRocket license information
+ item.setIcon(Icons.HELP_LICENSE);
item.getAccessibleContext().setAccessibleDescription(trans.get("main.menu.help.license.desc"));
item.addActionListener(new ActionListener() {
@Override
@@ -695,7 +689,7 @@ public class BasicFrame extends JFrame {
//// Bug report
item = new JMenuItem(trans.get("main.menu.help.bugReport"), KeyEvent.VK_B);
- //// Information about reporting bugs in OpenRocket
+ item.setIcon(Icons.HELP_BUG_REPORT);
item.getAccessibleContext().setAccessibleDescription(trans.get("main.menu.help.bugReport.desc"));
item.addActionListener(new ActionListener() {
@Override
@@ -708,7 +702,7 @@ public class BasicFrame extends JFrame {
//// Debug log
item = new JMenuItem(trans.get("main.menu.help.debugLog"));
- //// View the OpenRocket debug log
+ item.setIcon(Icons.HELP_DEBUG_LOG);
item.getAccessibleContext().setAccessibleDescription(trans.get("main.menu.help.debugLog.desc"));
item.addActionListener(new ActionListener() {
@Override
@@ -723,7 +717,7 @@ public class BasicFrame extends JFrame {
//// About
item = new JMenuItem(trans.get("main.menu.help.about"), KeyEvent.VK_A);
- //// About OpenRocket
+ item.setIcon(Icons.HELP_ABOUT);
item.getAccessibleContext().setAccessibleDescription(trans.get("main.menu.help.about.desc"));
item.addActionListener(new ActionListener() {
@Override
diff --git a/src/net/sf/openrocket/gui/plot/SimulationPlotDialog.java b/src/net/sf/openrocket/gui/plot/SimulationPlotDialog.java
index c68617b7a..e1e1b2d7a 100644
--- a/src/net/sf/openrocket/gui/plot/SimulationPlotDialog.java
+++ b/src/net/sf/openrocket/gui/plot/SimulationPlotDialog.java
@@ -461,6 +461,7 @@ public class SimulationPlotDialog extends JDialog {
this.pack();
GUIUtil.setDisposableDialogOptions(this, button);
+ GUIUtil.rememberWindowSize(this);
}
private String getLabel(FlightDataType type, Unit unit) {
diff --git a/src/net/sf/openrocket/models/gravity/WGSGravityModel.java b/src/net/sf/openrocket/models/gravity/WGSGravityModel.java
index 9339c0eb7..253d4a41d 100644
--- a/src/net/sf/openrocket/models/gravity/WGSGravityModel.java
+++ b/src/net/sf/openrocket/models/gravity/WGSGravityModel.java
@@ -10,13 +10,10 @@ import net.sf.openrocket.util.WorldCoordinate;
*/
public class WGSGravityModel implements GravityModel {
+ // Cache the previously computed value
private WorldCoordinate lastWorldCoordinate;
private double lastg;
-
- private static int hit = 0;
- private static int miss = 0;
-
@Override
public double getGravity(WorldCoordinate wc) {
@@ -25,12 +22,7 @@ public class WGSGravityModel implements GravityModel {
if (wc != this.lastWorldCoordinate) {
this.lastg = calcGravity(wc);
this.lastWorldCoordinate = wc;
-
- miss++;
- } else {
- hit++;
}
- System.out.println("GRAVITY MODEL: hit=" + hit + " miss=" + miss);
return this.lastg;
diff --git a/src/net/sf/openrocket/simulation/RK4SimulationStepper.java b/src/net/sf/openrocket/simulation/RK4SimulationStepper.java
index 4aa9494ce..eea602770 100644
--- a/src/net/sf/openrocket/simulation/RK4SimulationStepper.java
+++ b/src/net/sf/openrocket/simulation/RK4SimulationStepper.java
@@ -266,6 +266,9 @@ public class RK4SimulationStepper extends AbstractSimulationStepper {
if (status.getRocketVelocity().length2() > 1e18 ||
status.getRocketPosition().length2() > 1e18 ||
status.getRocketRotationVelocity().length2() > 1e18) {
+
+ // FIXME: Make error message better, recommend shortening time step
+
throw new SimulationCalculationException("Simulation values exceeded limits");
}
}
@@ -547,9 +550,9 @@ public class RK4SimulationStepper extends AbstractSimulationStepper {
data.setValue(FlightDataType.TYPE_POSITION_X, status.getRocketPosition().x);
data.setValue(FlightDataType.TYPE_POSITION_Y, status.getRocketPosition().y);
- if (status.getSimulationConditions().getGeodeticComputation() != GeodeticComputationStrategy.NONE) {
- data.setValue(FlightDataType.TYPE_LATITUDE, status.getRocketWorldPosition().getLatitudeRad());
- data.setValue(FlightDataType.TYPE_LONGITUDE, status.getRocketWorldPosition().getLongitudeRad());
+ data.setValue(FlightDataType.TYPE_LATITUDE, status.getRocketWorldPosition().getLatitudeRad());
+ data.setValue(FlightDataType.TYPE_LONGITUDE, status.getRocketWorldPosition().getLongitudeRad());
+ if (status.getSimulationConditions().getGeodeticComputation() != GeodeticComputationStrategy.FLAT) {
data.setValue(FlightDataType.TYPE_CORIOLIS_ACCELERATION, store.coriolisAcceleration.length());
}
diff --git a/src/net/sf/openrocket/simulation/SimulationOptions.java b/src/net/sf/openrocket/simulation/SimulationOptions.java
index 0c7f103a6..e6e8f821a 100644
--- a/src/net/sf/openrocket/simulation/SimulationOptions.java
+++ b/src/net/sf/openrocket/simulation/SimulationOptions.java
@@ -62,6 +62,7 @@ public class SimulationOptions implements ChangeSource, Cloneable {
private double windAverage = 2.0;
private double windTurbulence = 0.1;
+
/*
* SimulationOptions maintains the launch site parameters as separate double values,
* and converts them into a WorldCoordinate when converting to SimulationConditions.
diff --git a/src/net/sf/openrocket/util/GUIUtil.java b/src/net/sf/openrocket/util/GUIUtil.java
index d8aec3b0a..e74dbc196 100644
--- a/src/net/sf/openrocket/util/GUIUtil.java
+++ b/src/net/sf/openrocket/util/GUIUtil.java
@@ -2,6 +2,7 @@ package net.sf.openrocket.util;
import java.awt.Component;
import java.awt.Container;
+import java.awt.Dimension;
import java.awt.Font;
import java.awt.Image;
import java.awt.KeyboardFocusManager;
@@ -9,6 +10,8 @@ import java.awt.Point;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
+import java.awt.event.ComponentAdapter;
+import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import java.awt.event.FocusListener;
import java.awt.event.KeyEvent;
@@ -39,6 +42,7 @@ import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JDialog;
+import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JRootPane;
import javax.swing.JSlider;
@@ -267,6 +271,60 @@ public class GUIUtil {
}
+
+ /**
+ * Automatically remember the size of a window. This stores the window size in the user
+ * preferences when resizing/maximizing the window and sets the state on the first call.
+ */
+ public static void rememberWindowSize(final Window window) {
+ window.addComponentListener(new ComponentAdapter() {
+ @Override
+ public void componentResized(ComponentEvent e) {
+ log.debug("Storing size of " + window.getClass().getName() + ": " + window.getSize());
+ Prefs.setWindowSize(window.getClass(), window.getSize());
+ if (window instanceof JFrame) {
+ if ((((JFrame) window).getExtendedState() & JFrame.MAXIMIZED_BOTH) == JFrame.MAXIMIZED_BOTH) {
+ log.debug("Storing maximized state of " + window.getClass().getName());
+ Prefs.setWindowMaximized(window.getClass());
+ }
+ }
+ }
+ });
+
+ if (Prefs.isWindowMaximized(window.getClass())) {
+ if (window instanceof JFrame) {
+ ((JFrame) window).setExtendedState(JFrame.MAXIMIZED_BOTH);
+ }
+ } else {
+ Dimension dim = Prefs.getWindowSize(window.getClass());
+ if (dim != null) {
+ window.setSize(dim);
+ }
+ }
+ }
+
+
+ /**
+ * Automatically remember the position of a window. The position is stored in the user preferences
+ * every time the window is moved and set from there when first calling this method.
+ */
+ public static void rememberWindowPosition(final Window window) {
+ window.addComponentListener(new ComponentAdapter() {
+ @Override
+ public void componentMoved(ComponentEvent e) {
+ Prefs.setWindowPosition(window.getClass(), window.getLocation());
+ }
+ });
+
+ // Set window position according to preferences, and set prefs when moving
+ Point position = Prefs.getWindowPosition(window.getClass());
+ if (position != null) {
+ window.setLocationByPlatform(false);
+ window.setLocation(position);
+ }
+ }
+
+
/**
* Changes the style of the font of the specified border.
*
diff --git a/src/net/sf/openrocket/util/GeodeticComputationStrategy.java b/src/net/sf/openrocket/util/GeodeticComputationStrategy.java
index 8e7d2f299..6f1fb53aa 100644
--- a/src/net/sf/openrocket/util/GeodeticComputationStrategy.java
+++ b/src/net/sf/openrocket/util/GeodeticComputationStrategy.java
@@ -5,22 +5,41 @@ import net.sf.openrocket.startup.Application;
/**
* A strategy that performs computations on WorldCoordinates.
+ *
+ * The directions of the coordinate is:
+ * positive X = EAST
+ * positive Y = NORTH
+ * positive Z = UPWARDS
*/
public enum GeodeticComputationStrategy {
/**
- * Perform no geodetic computations. The addCoordinate method does nothing and
- * getCoriolisAcceleration returns Coordinate.NUL.
+ * Perform computations using a flat Earth approximation. addCoordinate computes the
+ * location using a direct meters-per-degree scaling and getCoriolisAcceleration always
+ * returns NUL.
*/
- NONE {
+ FLAT {
+ private static final double METERS_PER_DEGREE_LATITUDE = 111325; // "standard figure"
+ private static final double METERS_PER_DEGREE_LONGITUDE_EQUATOR = 111050;
+
+
@Override
- public WorldCoordinate addCoordinate(WorldCoordinate latlon, Coordinate delta) {
- return latlon;
+ public WorldCoordinate addCoordinate(WorldCoordinate location, Coordinate delta) {
+
+ double metersPerDegreeLongitude = METERS_PER_DEGREE_LONGITUDE_EQUATOR * Math.cos(location.getLatitudeRad());
+ // Limit to 1 meter per degree near poles
+ metersPerDegreeLongitude = MathUtil.max(metersPerDegreeLongitude, 1);
+
+ double newLat = location.getLatitudeDeg() + delta.y / METERS_PER_DEGREE_LATITUDE;
+ double newLon = location.getLongitudeDeg() + delta.x / metersPerDegreeLongitude;
+ double newAlt = location.getAltitude() + delta.z;
+
+ return new WorldCoordinate(newLat, newLon, newAlt);
}
@Override
- public Coordinate getCoriolisAcceleration(WorldCoordinate latlon, Coordinate velocity) {
+ public Coordinate getCoriolisAcceleration(WorldCoordinate location, Coordinate velocity) {
return Coordinate.NUL;
}
},
@@ -37,9 +56,13 @@ public enum GeodeticComputationStrategy {
// bearing (in radians, clockwise from north);
// d/R is the angular distance (in radians), where d is the distance traveled and R is the earth’s radius
double d = MathUtil.hypot(delta.x, delta.y);
- double bearing = Math.atan(delta.x / delta.y);
- if (delta.y < 0)
- bearing = bearing + Math.PI;
+
+ // Check for zero movement before computing bearing
+ if (MathUtil.equals(d, 0)) {
+ return new WorldCoordinate(location.getLatitudeDeg(), location.getLongitudeDeg(), newAlt);
+ }
+
+ double bearing = Math.atan2(delta.x, delta.y);
// Calculate the new lat and lon
double newLat, newLon;
@@ -51,20 +74,17 @@ public enum GeodeticComputationStrategy {
newLat = Math.asin(sinLat * cosDR + cosLat * sinDR * Math.cos(bearing));
newLon = location.getLongitudeRad() + Math.atan2(Math.sin(bearing) * sinDR * cosLat, cosDR - sinLat * Math.sin(newLat));
- if (Double.isNaN(newLat)) {
- newLat = location.getLatitudeRad();
- }
-
- if (Double.isNaN(newLon)) {
- newLon = location.getLongitudeRad();
+ if (Double.isNaN(newLat) || Double.isNaN(newLon)) {
+ throw new BugException("addCoordinate resulted in NaN location: location=" + location + " delta=" + delta
+ + " newLat=" + newLat + " newLon=" + newLon);
}
return new WorldCoordinate(Math.toDegrees(newLat), Math.toDegrees(newLon), newAlt);
}
@Override
- public Coordinate getCoriolisAcceleration(WorldCoordinate latlon, Coordinate velocity) {
- return computeCoriolisAcceleration(latlon, velocity);
+ public Coordinate getCoriolisAcceleration(WorldCoordinate location, Coordinate velocity) {
+ return computeCoriolisAcceleration(location, velocity);
}
},
@@ -81,6 +101,12 @@ public enum GeodeticComputationStrategy {
// bearing (in radians, clockwise from north);
// d/R is the angular distance (in radians), where d is the distance traveled and R is the earth’s radius
double d = MathUtil.hypot(delta.x, delta.y);
+
+ // Check for zero movement before computing bearing
+ if (MathUtil.equals(d, 0)) {
+ return new WorldCoordinate(location.getLatitudeDeg(), location.getLongitudeDeg(), newAlt);
+ }
+
double bearing = Math.atan(delta.x / delta.y);
if (delta.y < 0)
bearing = bearing + Math.PI;
@@ -91,19 +117,17 @@ public enum GeodeticComputationStrategy {
newLat = ret[0];
newLon = ret[1];
- if (Double.isNaN(newLat)) {
- newLat = location.getLatitudeRad();
+ if (Double.isNaN(newLat) || Double.isNaN(newLon)) {
+ throw new BugException("addCoordinate resulted in NaN location: location=" + location + " delta=" + delta
+ + " newLat=" + newLat + " newLon=" + newLon);
}
- if (Double.isNaN(newLon)) {
- newLon = location.getLongitudeRad();
- }
- return new WorldCoordinate(newLat, newLon, newAlt);
+ return new WorldCoordinate(Math.toDegrees(newLat), Math.toDegrees(newLon), newAlt);
}
@Override
- public Coordinate getCoriolisAcceleration(WorldCoordinate latlon, Coordinate velocity) {
- return computeCoriolisAcceleration(latlon, velocity);
+ public Coordinate getCoriolisAcceleration(WorldCoordinate location, Coordinate velocity) {
+ return computeCoriolisAcceleration(location, velocity);
}
};
@@ -136,13 +160,13 @@ public enum GeodeticComputationStrategy {
/**
* Add a cartesian movement coordinate to a WorldCoordinate.
*/
- public abstract WorldCoordinate addCoordinate(WorldCoordinate latlon, Coordinate delta);
+ public abstract WorldCoordinate addCoordinate(WorldCoordinate location, Coordinate delta);
/**
* Compute the coriolis acceleration at a specified WorldCoordinate and velocity.
*/
- public abstract Coordinate getCoriolisAcceleration(WorldCoordinate latlon, Coordinate velocity);
+ public abstract Coordinate getCoriolisAcceleration(WorldCoordinate location, Coordinate velocity);
diff --git a/src/net/sf/openrocket/util/Icons.java b/src/net/sf/openrocket/util/Icons.java
index 7639bcdc7..e5f41317a 100644
--- a/src/net/sf/openrocket/util/Icons.java
+++ b/src/net/sf/openrocket/util/Icons.java
@@ -62,6 +62,11 @@ public class Icons {
public static final Icon EDIT_DELETE = loadImageIcon("pix/icons/edit-delete.png", "Delete");
public static final Icon EDIT_SCALE = loadImageIcon("pix/icons/edit-scale.png", "Scale");
+ public static final Icon HELP_ABOUT = loadImageIcon("pix/icons/help-about.png", "About");
+ public static final Icon HELP_BUG_REPORT = loadImageIcon("pix/icons/help-bug.png", "Bug report");
+ public static final Icon HELP_DEBUG_LOG = loadImageIcon("pix/icons/help-log.png", "Debug log");
+ public static final Icon HELP_LICENSE = loadImageIcon("pix/icons/help-license.png", "License");
+
public static final Icon ZOOM_IN = loadImageIcon("pix/icons/zoom-in.png", "Zoom in");
public static final Icon ZOOM_OUT = loadImageIcon("pix/icons/zoom-out.png", "Zoom out");
diff --git a/src/net/sf/openrocket/util/Prefs.java b/src/net/sf/openrocket/util/Prefs.java
index 2d59f7768..dfe05a1d4 100644
--- a/src/net/sf/openrocket/util/Prefs.java
+++ b/src/net/sf/openrocket/util/Prefs.java
@@ -38,8 +38,8 @@ 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.SimulationOptions;
import net.sf.openrocket.simulation.RK4SimulationStepper;
+import net.sf.openrocket.simulation.SimulationOptions;
import net.sf.openrocket.startup.Application;
import net.sf.openrocket.unit.UnitGroup;
@@ -715,11 +715,22 @@ public class Prefs {
return new Dimension(x, y);
}
+
+ public static boolean isWindowMaximized(Class> c) {
+ String pref = PREFNODE.node("windows").get("size." + c.getCanonicalName(), null);
+ return "max".equals(pref);
+ }
+
public static void setWindowSize(Class> c, Dimension d) {
PREFNODE.node("windows").put("size." + c.getCanonicalName(), "" + d.width + "," + d.height);
storeVersion();
}
+ public static void setWindowMaximized(Class> c) {
+ PREFNODE.node("windows").put("size." + c.getCanonicalName(), "max");
+ storeVersion();
+ }
+
//// Printing
diff --git a/src/net/sf/openrocket/util/WorldCoordinate.java b/src/net/sf/openrocket/util/WorldCoordinate.java
index b3a047fde..762b02958 100644
--- a/src/net/sf/openrocket/util/WorldCoordinate.java
+++ b/src/net/sf/openrocket/util/WorldCoordinate.java
@@ -15,9 +15,10 @@ public class WorldCoordinate {
/**
* Constructs a new WorldCoordinate
+ *
* @param lat latitude in degrees north. From -90 to 90, values outside are clamped.
* @param lon longitude in degrees east. From -180 to 180, values outside are reduced to the range.
- * @param alt altitude in m. Unbounded.
+ * @param alt altitude in meters. Unbounded.
*/
public WorldCoordinate(double lat, double lon, double alt) {
this.lat = MathUtil.clamp(Math.toRadians(lat), -Math.PI / 2, Math.PI / 2);
@@ -26,33 +27,35 @@ public class WorldCoordinate {
}
-
+ /**
+ * Returns the altitude.
+ */
public double getAltitude() {
return this.alt;
}
- /*
+ /**
* Returns Longitude in radians
*/
public double getLongitudeRad() {
return this.lon;
}
- /*
+ /**
* Returns Longitude in degrees
*/
public double getLongitudeDeg() {
return Math.toDegrees(this.lon);
}
- /*
+ /**
* Returns latitude in radians
*/
public double getLatitudeRad() {
return this.lat;
}
- /*
+ /**
* Returns latitude in degrees
*/
public double getLatitudeDeg() {
@@ -60,9 +63,10 @@ public class WorldCoordinate {
}
+
@Override
public String toString() {
- return "WorldCoordinate[lat=" + lat + ", lon=" + lon + ", alt=" + alt + "]";
+ return "WorldCoordinate[lat=" + getLatitudeDeg() + ", lon=" + getLongitudeDeg() + ", alt=" + getAltitude() + "]";
}
diff --git a/test/net/sf/openrocket/util/GeodeticComputationStrategyTest.java b/test/net/sf/openrocket/util/GeodeticComputationStrategyTest.java
index 7adf21c8f..72800d01f 100644
--- a/test/net/sf/openrocket/util/GeodeticComputationStrategyTest.java
+++ b/test/net/sf/openrocket/util/GeodeticComputationStrategyTest.java
@@ -7,7 +7,7 @@ import org.junit.Test;
public class GeodeticComputationStrategyTest {
@Test
- public void testAddCoordinate() {
+ public void testSpericalAddCoordinate() {
double arcmin = (1.0 / 60.0);
double arcsec = (1.0 / (60.0 * 60.0));
@@ -36,8 +36,109 @@ public class GeodeticComputationStrategyTest {
assertEquals(1000.0, wc.getAltitude(), 0.0);
}
+
@Test
- public void testGetCoriolisAcceleration1() {
+ public void testAddCoordinates() {
+
+ double min = 1 / 60.0;
+ double sec = 1 / 3600.0;
+
+
+ // Test zero movement
+ testAddCoordinate(50.0, 20.0, 0, 123, 50.0, 20.0, false);
+
+
+ /*
+ * These example values have been computed using the calculator at
+ * http://www.movable-type.co.uk/scripts/latlong.html
+ */
+
+ // Long distance NE over England, crosses Greenwich meridian
+ // 50 03N 005 42W to 58 38N 003 04E is 1109km at 027 16'07"
+ testAddCoordinate(50 + 3 * min, -5 - 42 * min, 1109000, 27 + 16 * min + 7 * sec, 58 + 38 * min, 3 + 4 * min, false);
+
+ // SW over Brazil
+ // -10N -60E to -11N -61E is 155.9km at 224 25'34"
+ testAddCoordinate(-10, -60, 155900, 224 + 25 * min + 34 * sec, -11, -61, true);
+
+ // NW over the 180 meridian
+ // 63N -179E to 63 01N 179E is 100.9km at 271 56'34"
+ testAddCoordinate(63, -179, 100900, 271 + 56 * min + 34 * sec, 63 + 1 * min, 179, true);
+
+ // NE near the north pole
+ // 89 50N 0E to 89 45N 175E is 46.29 km at 003 00'01"
+ testAddCoordinate(89 + 50 * min, 0, 46290, 3 + 0 * min + 1 * sec, 89 + 45 * min, 175, false);
+
+ // S directly over south pole
+ // -89 50N 12E to -89 45N 192E is 46.33km at 180 00'00"
+ testAddCoordinate(-89 - 50 * min, 12, 46330, 180, -89 - 45 * min, -168, false);
+
+ }
+
+ private void testAddCoordinate(double initialLatitude, double initialLongitude, double distance, double bearing,
+ double finalLatitude, double finalLongitude, boolean testFlat) {
+
+ double tolerance;
+
+ bearing = Math.toRadians(bearing);
+
+ // positive X is EAST, positive Y is NORTH
+ double deltaX = distance * Math.sin(bearing);
+ double deltaY = distance * Math.cos(bearing);
+
+ Coordinate coord = new Coordinate(deltaX, deltaY, 1000.0);
+ WorldCoordinate wc = new WorldCoordinate(initialLatitude, initialLongitude, 0.0);
+
+ // Test SPHERICAL
+ tolerance = 0.0015 * distance / 111325;
+ System.out.println("\nSpherical tolerance: " + tolerance);
+ WorldCoordinate result = GeodeticComputationStrategy.SPHERICAL.addCoordinate(wc, coord);
+
+ System.out.println("Difference Lat: " + Math.abs(finalLatitude - result.getLatitudeDeg()));
+ System.out.println("Difference Lon: " + Math.abs(finalLongitude - result.getLongitudeDeg()));
+ assertEquals(finalLatitude, result.getLatitudeDeg(), tolerance);
+ assertEquals(finalLongitude, result.getLongitudeDeg(), tolerance);
+ assertEquals(1000.0, result.getAltitude(), 0.0);
+
+
+ // Test WGS84
+ /*
+ * TODO: Since the example values are computed using a spherical earth approximation,
+ * the WGS84 method will have significantly larger errors. The tolerance should be
+ * increased correspondingly.
+ */
+ //tolerance = ...
+ System.out.println("\nWGS84 tolerance: " + tolerance);
+ result = GeodeticComputationStrategy.WGS84.addCoordinate(result, coord);
+
+ System.out.println("Difference Lat: " + Math.abs(finalLatitude - result.getLatitudeDeg()));
+ System.out.println("Difference Lon: " + Math.abs(finalLongitude - result.getLongitudeDeg()));
+ // FIXME: Re-enable these when they function
+ // assertEquals(finalLatitude, result.getLatitudeDeg(), tolerance);
+ // assertEquals(finalLongitude, result.getLongitudeDeg(), tolerance);
+ // assertEquals(1000.0, result.getAltitude(), 0.0);
+
+
+ // Test FLAT
+ if (testFlat) {
+ tolerance = 0.02 * distance / 111325;
+ System.out.println("\nFlat tolerance: " + tolerance);
+ result = GeodeticComputationStrategy.FLAT.addCoordinate(wc, coord);
+
+ System.out.println("Difference Lat: " + Math.abs(finalLatitude - result.getLatitudeDeg()));
+ System.out.println("Difference Lon: " + Math.abs(finalLongitude - result.getLongitudeDeg()));
+ assertEquals(finalLatitude, result.getLatitudeDeg(), tolerance);
+ assertEquals(finalLongitude, result.getLongitudeDeg(), tolerance);
+ assertEquals(1000.0, result.getAltitude(), 0.0);
+
+ }
+
+ }
+
+
+
+ @Test
+ public void testSpericalGetCoriolisAcceleration() {
// For positive latitude and rotational velocity, a movement due east results in an acceleration due south
Coordinate velocity = new Coordinate(-1000, 0, 0);