package net.sf.openrocket.gui.configdialog;
-import java.awt.event.ActionEvent;
-import java.awt.event.ActionListener;
-
-import javax.swing.JButton;
-import javax.swing.JComboBox;
-import javax.swing.JLabel;
-import javax.swing.JPanel;
-import javax.swing.JSpinner;
-import javax.swing.SwingUtilities;
-
import net.miginfocom.swing.MigLayout;
import net.sf.openrocket.gui.SpinnerEditor;
import net.sf.openrocket.gui.adaptors.DoubleModel;
import net.sf.openrocket.gui.components.UnitSelector;
import net.sf.openrocket.l10n.Translator;
import net.sf.openrocket.logging.LogHelper;
+import net.sf.openrocket.rocketcomponent.CenteringRing;
+import net.sf.openrocket.rocketcomponent.Coaxial;
import net.sf.openrocket.rocketcomponent.FinSet;
import net.sf.openrocket.rocketcomponent.FreeformFinSet;
+import net.sf.openrocket.rocketcomponent.InnerTube;
import net.sf.openrocket.rocketcomponent.RocketComponent;
import net.sf.openrocket.startup.Application;
import net.sf.openrocket.unit.UnitGroup;
+import javax.swing.*;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
public abstract class FinSetConfig extends RocketComponentConfig {
- private static final LogHelper log = Application.getLogger();
- private static final Translator trans = Application.getTranslator();
-
- private JButton split = null;
-
- public FinSetConfig(RocketComponent component) {
- super(component);
-
- //// Fin tabs and Through-the-wall fin tabs
- tabbedPane.insertTab(trans.get("FinSetConfig.tab.Fintabs"), null, finTabPanel(),
- trans.get("FinSetConfig.tab.Through-the-wall"), 0);
- }
-
-
-
- protected void addFinSetButtons() {
- JButton convert = null;
-
- //// Convert buttons
- if (!(component instanceof FreeformFinSet)) {
- //// Convert to freeform
- convert = new JButton(trans.get("FinSetConfig.but.Converttofreeform"));
- //// Convert this fin set into a freeform fin set
- convert.setToolTipText(trans.get("FinSetConfig.but.Converttofreeform.ttip"));
- convert.addActionListener(new ActionListener() {
- @Override
- public void actionPerformed(ActionEvent e) {
- log.user("Converting " + component.getComponentName() + " into freeform fin set");
-
- // Do change in future for overall safety
- SwingUtilities.invokeLater(new Runnable() {
- @Override
- public void run() {
- //// Convert fin set
- ComponentConfigDialog.addUndoPosition(trans.get("FinSetConfig.Convertfinset"));
- RocketComponent freeform =
- FreeformFinSet.convertFinSet((FinSet) component);
- ComponentConfigDialog.showDialog(freeform);
- }
- });
-
- ComponentConfigDialog.hideDialog();
- }
- });
- }
-
- //// Split fins
- split = new JButton(trans.get("FinSetConfig.but.Splitfins"));
- //// Split the fin set into separate fins
- split.setToolTipText(trans.get("FinSetConfig.but.Splitfins.ttip"));
- split.addActionListener(new ActionListener() {
- @Override
- public void actionPerformed(ActionEvent e) {
- log.user("Splitting " + component.getComponentName() + " into separate fins, fin count=" +
- ((FinSet) component).getFinCount());
-
- // Do change in future for overall safety
- SwingUtilities.invokeLater(new Runnable() {
- @Override
- public void run() {
- RocketComponent parent = component.getParent();
- int index = parent.getChildPosition(component);
- int count = ((FinSet) component).getFinCount();
- double base = ((FinSet) component).getBaseRotation();
- if (count <= 1)
- return;
-
- ComponentConfigDialog.addUndoPosition("Split fin set");
- parent.removeChild(index);
- for (int i = 0; i < count; i++) {
- FinSet copy = (FinSet) component.copy();
- copy.setFinCount(1);
- copy.setBaseRotation(base + i * 2 * Math.PI / count);
- copy.setName(copy.getName() + " #" + (i + 1));
- parent.addChild(copy, index + i);
- }
- }
- });
-
- ComponentConfigDialog.hideDialog();
- }
- });
- split.setEnabled(((FinSet) component).getFinCount() > 1);
-
- if (convert == null)
- addButtons(split);
- else
- addButtons(split, convert);
-
- }
-
- public JPanel finTabPanel() {
- JPanel panel = new JPanel(
- new MigLayout("align 50% 20%, fillx, gap rel unrel, ins 20lp 10% 20lp 10%",
- "[150lp::][65lp::][30lp::][200lp::]", ""));
- // JPanel panel = new JPanel(new MigLayout("fillx, align 20% 20%, gap rel unrel",
- // "[40lp][80lp::][30lp::][100lp::]",""));
-
- //// Through-the-wall fin tabs:
- panel.add(new StyledLabel(trans.get("FinSetConfig.lbl.Through-the-wall"), Style.BOLD),
- "spanx, wrap 30lp");
-
- JLabel label;
- DoubleModel m;
- DoubleModel length;
- DoubleModel length2;
- DoubleModel length_2;
- JSpinner spin;
-
- length = new DoubleModel(component, "Length", UnitGroup.UNITS_LENGTH, 0);
- length2 = new DoubleModel(component, "Length", 0.5, UnitGroup.UNITS_LENGTH, 0);
- length_2 = new DoubleModel(component, "Length", -0.5, UnitGroup.UNITS_LENGTH, 0);
-
- register(length);
- register(length2);
- register(length_2);
-
- //// Tab length
- //// Tab length:
- label = new JLabel(trans.get("FinSetConfig.lbl.Tablength"));
- //// The length of the fin tab.
- label.setToolTipText(trans.get("FinSetConfig.ttip.Tablength"));
- panel.add(label, "gapleft para, gapright 40lp, growx 1");
-
- m = new DoubleModel(component, "TabLength", UnitGroup.UNITS_LENGTH, 0);
-
- spin = new JSpinner(m.getSpinnerModel());
- spin.setEditor(new SpinnerEditor(spin));
- panel.add(spin, "growx 1");
-
- panel.add(new UnitSelector(m), "growx 1");
- panel.add(new BasicSlider(m.getSliderModel(DoubleModel.ZERO, length)),
- "w 100lp, growx 5, wrap");
-
-
- //// Tab length
- //// Tab height:
- label = new JLabel(trans.get("FinSetConfig.lbl.Tabheight"));
- //// The spanwise height of the fin tab.
- label.setToolTipText(trans.get("FinSetConfig.ttip.Tabheight"));
- panel.add(label, "gapleft para");
-
- m = new DoubleModel(component, "TabHeight", UnitGroup.UNITS_LENGTH, 0);
-
- spin = new JSpinner(m.getSpinnerModel());
- spin.setEditor(new SpinnerEditor(spin));
- panel.add(spin, "growx");
-
- panel.add(new UnitSelector(m), "growx");
- panel.add(new BasicSlider(m.getSliderModel(DoubleModel.ZERO, length2)),
- "w 100lp, growx 5, wrap para");
-
-
- //// Tab position:
- label = new JLabel(trans.get("FinSetConfig.lbl.Tabposition"));
- //// The position of the fin tab.
- label.setToolTipText(trans.get("FinSetConfig.ttip.Tabposition"));
- panel.add(label, "gapleft para");
-
- m = new DoubleModel(component, "TabShift", UnitGroup.UNITS_LENGTH);
-
- spin = new JSpinner(m.getSpinnerModel());
- spin.setEditor(new SpinnerEditor(spin));
- panel.add(spin, "growx");
-
- panel.add(new UnitSelector(m), "growx");
- panel.add(new BasicSlider(m.getSliderModel(length_2, length2)), "w 100lp, growx 5, wrap");
-
-
- //// relative to
- label = new JLabel(trans.get("FinSetConfig.lbl.relativeto"));
- panel.add(label, "right, gapright unrel");
-
- EnumModel<FinSet.TabRelativePosition> em =
- new EnumModel<FinSet.TabRelativePosition>(component, "TabRelativePosition");
-
- panel.add(new JComboBox(em), "spanx 3, growx");
-
- return panel;
- }
-
- @Override
- public void updateFields() {
- super.updateFields();
- if (split != null)
- split.setEnabled(((FinSet) component).getFinCount() > 1);
- }
+ private static final LogHelper log = Application.getLogger();
+ private static final Translator trans = Application.getTranslator();
+
+ private JButton split = null;
+
+ public FinSetConfig(RocketComponent component) {
+ super(component);
+
+ //// Fin tabs and Through-the-wall fin tabs
+ tabbedPane.insertTab(trans.get("FinSetConfig.tab.Fintabs"), null, finTabPanel(),
+ trans.get("FinSetConfig.tab.Through-the-wall"), 0);
+ }
+
+
+ protected void addFinSetButtons() {
+ JButton convert = null;
+
+ //// Convert buttons
+ if (!(component instanceof FreeformFinSet)) {
+ //// Convert to freeform
+ convert = new JButton(trans.get("FinSetConfig.but.Converttofreeform"));
+ //// Convert this fin set into a freeform fin set
+ convert.setToolTipText(trans.get("FinSetConfig.but.Converttofreeform.ttip"));
+ convert.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ log.user("Converting " + component.getComponentName() + " into freeform fin set");
+
+ // Do change in future for overall safety
+ SwingUtilities.invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ //// Convert fin set
+ ComponentConfigDialog.addUndoPosition(trans.get("FinSetConfig.Convertfinset"));
+ RocketComponent freeform =
+ FreeformFinSet.convertFinSet((FinSet) component);
+ ComponentConfigDialog.showDialog(freeform);
+ }
+ });
+
+ ComponentConfigDialog.hideDialog();
+ }
+ });
+ }
+
+ //// Split fins
+ split = new JButton(trans.get("FinSetConfig.but.Splitfins"));
+ //// Split the fin set into separate fins
+ split.setToolTipText(trans.get("FinSetConfig.but.Splitfins.ttip"));
+ split.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ log.user("Splitting " + component.getComponentName() + " into separate fins, fin count=" +
+ ((FinSet) component).getFinCount());
+
+ // Do change in future for overall safety
+ SwingUtilities.invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ RocketComponent parent = component.getParent();
+ int index = parent.getChildPosition(component);
+ int count = ((FinSet) component).getFinCount();
+ double base = ((FinSet) component).getBaseRotation();
+ if (count <= 1)
+ return;
+
+ ComponentConfigDialog.addUndoPosition("Split fin set");
+ parent.removeChild(index);
+ for (int i = 0; i < count; i++) {
+ FinSet copy = (FinSet) component.copy();
+ copy.setFinCount(1);
+ copy.setBaseRotation(base + i * 2 * Math.PI / count);
+ copy.setName(copy.getName() + " #" + (i + 1));
+ parent.addChild(copy, index + i);
+ }
+ }
+ });
+
+ ComponentConfigDialog.hideDialog();
+ }
+ });
+ split.setEnabled(((FinSet) component).getFinCount() > 1);
+
+ if (convert == null)
+ addButtons(split);
+ else
+ addButtons(split, convert);
+
+ }
+
+ public JPanel finTabPanel() {
+ JPanel panel = new JPanel(
+ new MigLayout("align 50% 20%, fillx, gap rel unrel, ins 20lp 10% 20lp 10%",
+ "[150lp::][65lp::][30lp::][200lp::]", ""));
+ // JPanel panel = new JPanel(new MigLayout("fillx, align 20% 20%, gap rel unrel",
+ // "[40lp][80lp::][30lp::][100lp::]",""));
+
+ //// Through-the-wall fin tabs:
+ panel.add(new StyledLabel(trans.get("FinSetConfig.lbl.Through-the-wall"), Style.BOLD),
+ "spanx, wrap 30lp");
+
+ JLabel label;
+ DoubleModel length;
+ DoubleModel length2;
+ DoubleModel length_2;
+ JSpinner spin;
+ JButton calcHeight;
+
+ length = new DoubleModel(component, "Length", UnitGroup.UNITS_LENGTH, 0);
+ length2 = new DoubleModel(component, "Length", 0.5, UnitGroup.UNITS_LENGTH, 0);
+ length_2 = new DoubleModel(component, "Length", -0.5, UnitGroup.UNITS_LENGTH, 0);
+
+ register(length);
+ register(length2);
+ register(length_2);
+
+ //// Tab length
+ //// Tab length:
+ label = new JLabel(trans.get("FinSetConfig.lbl.Tablength"));
+ //// The length of the fin tab.
+ label.setToolTipText(trans.get("FinSetConfig.ttip.Tablength"));
+ panel.add(label, "gapleft para, gapright 40lp, growx 1");
+
+ final DoubleModel mtl = new DoubleModel(component, "TabLength", UnitGroup.UNITS_LENGTH, 0);
+
+ spin = new JSpinner(mtl.getSpinnerModel());
+ spin.setEditor(new SpinnerEditor(spin));
+ panel.add(spin, "growx 1");
+
+ panel.add(new UnitSelector(mtl), "growx 1");
+ panel.add(new BasicSlider(mtl.getSliderModel(DoubleModel.ZERO, length)),
+ "w 100lp, growx 5, wrap");
+
+
+ //// Tab length
+ //// Tab height:
+ label = new JLabel(trans.get("FinSetConfig.lbl.Tabheight"));
+ //// The spanwise height of the fin tab.
+ label.setToolTipText(trans.get("FinSetConfig.ttip.Tabheight"));
+ panel.add(label, "gapleft para");
+
+ final DoubleModel mth = new DoubleModel(component, "TabHeight", UnitGroup.UNITS_LENGTH, 0);
+
+ spin = new JSpinner(mth.getSpinnerModel());
+ spin.setEditor(new SpinnerEditor(spin));
+ panel.add(spin, "growx");
+
+ panel.add(new UnitSelector(mth), "growx");
+ panel.add(new BasicSlider(mth.getSliderModel(DoubleModel.ZERO, length2)),
+ "w 100lp, growx 5, wrap para");
+
+ //// Tab position:
+ label = new JLabel(trans.get("FinSetConfig.lbl.Tabposition"));
+ //// The position of the fin tab.
+ label.setToolTipText(trans.get("FinSetConfig.ttip.Tabposition"));
+ panel.add(label, "gapleft para");
+
+ final DoubleModel mts = new DoubleModel(component, "TabShift", UnitGroup.UNITS_LENGTH);
+
+ spin = new JSpinner(mts.getSpinnerModel());
+ spin.setEditor(new SpinnerEditor(spin));
+ panel.add(spin, "growx");
+
+ panel.add(new UnitSelector(mts), "growx");
+ panel.add(new BasicSlider(mts.getSliderModel(length_2, length2)), "w 100lp, growx 5, wrap");
+
+
+ //// relative to
+ label = new JLabel(trans.get("FinSetConfig.lbl.relativeto"));
+ panel.add(label, "right, gapright unrel");
+
+ final EnumModel<FinSet.TabRelativePosition> em =
+ new EnumModel<FinSet.TabRelativePosition>(component, "TabRelativePosition");
+
+ panel.add(new JComboBox(em), "spanx 3, growx");
+
+ calcHeight = new JButton(trans.get("FinSetConfig.but.Calcheight"));
+
+ // Calculate fin tab height, length, and position
+ calcHeight.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ log.user("Computing " + component.getComponentName() + " tab height.");
+
+ // Do change in future for overall safety
+ SwingUtilities.invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ RocketComponent parent = component.getParent();
+ if (parent instanceof Coaxial) {
+ List<RocketComponent> children = parent.getChildren();
+ List<CenteringRing> rings = new ArrayList<CenteringRing>();
+
+ ComponentConfigDialog.addUndoPosition("Compute fin tab height");
+ for (int i = 0; i < children.size(); i++) {
+ RocketComponent rocketComponent = children.get(i);
+ if (rocketComponent instanceof InnerTube) {
+ InnerTube it = (InnerTube) rocketComponent;
+ if (it.isMotorMount()) {
+ double depth = ((Coaxial) parent).getOuterRadius() - it.getOuterRadius();
+ //Set fin tab depth
+ if (depth >= 0.0d) {
+ mth.setValue(depth);
+ mth.setCurrentUnit(UnitGroup.UNITS_LENGTH.getDefaultUnit());
+ }
+ }
+ } else if (rocketComponent instanceof CenteringRing) {
+ rings.add((CenteringRing) rocketComponent);
+ }
+ }
+ //Figure out position and length of the fin tab
+ if (!rings.isEmpty()) {
+ FinSet.TabRelativePosition temp = (FinSet.TabRelativePosition) em.getSelectedItem();
+ em.setSelectedItem(FinSet.TabRelativePosition.FRONT);
+ double len = computeFinTabLength(rings, component.asPositionValue(RocketComponent.Position.TOP),
+ component.getLength(), mts);
+ mtl.setValue(len);
+ //Be nice to the user and set the tab relative position enum back the way they had it.
+ em.setSelectedItem(temp);
+ }
+ }
+ }
+ });
+ }
+ });
+ panel.add(calcHeight, "right, gapright unrel");
+
+ return panel;
+ }
+
+ /**
+ * Scenarios:
+ * <p/>
+ * 1. All rings ahead of start of fin.
+ * 2. First ring ahead of start of fin. Second ring ahead of end of fin.
+ * 3. First ring ahead of start of fin. Second ring behind end of fin.
+ * 4. First ring equal or behind start of fin. Second ring ahead of, or equal to, end of fin.
+ * 5. First ring equal or behind start of fin. Second ring behind end of fin.
+ * 6. All rings behind end of fin.
+ *
+ * @param rings an unordered list of centering rings attached to the parent of the fin set
+ * @param finPositionFromTop the position from the top of the parent of the start of the fin set root
+ * @param finLength the length of the root chord
+ * @param mts the model for the tab shift (position); the model's value is modified as a result of this method call
+ * @return the length of the fin tab
+ */
+ private static double computeFinTabLength(List<CenteringRing> rings, Double finPositionFromTop, Double finLength, DoubleModel mts) {
+ List<SortableRing> positionsFromTop = new ArrayList<SortableRing>();
+
+ //Fin tabs will be computed between the last two rings that meet the criteria, represented by top and bottom here.
+ SortableRing top = null;
+ SortableRing bottom = null;
+
+ if (rings != null) {
+ //Sort rings from top of parent to bottom
+ Collections.sort(rings, new Comparator<CenteringRing>() {
+ @Override
+ public int compare(CenteringRing centeringRing, CenteringRing centeringRing1) {
+ return (int) (1000d * (centeringRing.asPositionValue(RocketComponent.Position.TOP) -
+ centeringRing1.asPositionValue(RocketComponent.Position.TOP)));
+ }
+ });
+
+ for (int i = 0; i < rings.size(); i++) {
+ CenteringRing centeringRing = rings.get(i);
+ //Handle centering rings that overlap or are adjacent by synthetically merging them into one virtual ring.
+ if (!positionsFromTop.isEmpty() &&
+ positionsFromTop.get(positionsFromTop.size() - 1).bottomSidePositionFromTop() >= centeringRing.asPositionValue(RocketComponent.Position.TOP)) {
+ SortableRing adjacent = positionsFromTop.get(positionsFromTop.size() - 1);
+ adjacent.merge(centeringRing);
+ } else {
+ positionsFromTop.add(new SortableRing(centeringRing));
+ }
+ }
+
+ for (int i = 0; i < positionsFromTop.size(); i++) {
+ SortableRing sortableRing = positionsFromTop.get(i);
+ if (top == null) {
+ top = sortableRing;
+ } else if (sortableRing.bottomSidePositionFromTop() <= finPositionFromTop) {
+ top = sortableRing;
+ bottom = null;
+ } else if (top.bottomSidePositionFromTop() <= finPositionFromTop) {
+ if (bottom == null) {
+ //If the current ring is in the upper half of the root chord, make it the top ring
+ if (sortableRing.bottomSidePositionFromTop() < finPositionFromTop + finLength / 2d) {
+ top = sortableRing;
+ } else {
+ bottom = sortableRing;
+ }
+ }
+ //Is the ring even with or above the end of the root chord? If so, make the existing bottom the top ring,
+ //and the current ring the bottom
+ else if (sortableRing.positionFromTop() <= finPositionFromTop + finLength) {
+ top = bottom;
+ bottom = sortableRing;
+ }
+ } else {
+ if (bottom == null) {
+ bottom = sortableRing;
+ }
+ }
+ }
+ }
+
+ // Edge case where there are no centering rings or for some odd reason top and bottom are identical.
+ if (top == null || top == bottom) {
+ mts.setValue(0);
+ return finLength;
+ }
+
+ if (bottom == null) {
+ // If there is no bottom ring and the top ring's bottom edge is within the span of the root chord, then
+ // set the position of the fin tab starting at the bottom side of the top ring.
+ if (top.bottomSidePositionFromTop() >= finPositionFromTop) {
+ mts.setValue(top.bottomSidePositionFromTop() - finPositionFromTop);
+ return (finPositionFromTop + finLength - top.bottomSidePositionFromTop());
+ } else {
+ // Otherwise the top ring is outside the span of the root chord so set the tab length to be the entire
+ // root chord.
+ mts.setValue(0);
+ return finLength;
+ }
+ }
+ // If the bottom edge of the top centering ring is above the start of the fin's root chord, then make the
+ // fin tab align with the start of the root chord.
+ if (top.bottomSidePositionFromTop() < finPositionFromTop) {
+ mts.setValue(0);
+ return bottom.positionFromTop - finPositionFromTop;
+ } else {
+ // Otherwise the rings are within the span of the root chord. Place the tab between them.
+ mts.setValue(top.bottomSidePositionFromTop() - finPositionFromTop);
+ return (bottom.positionFromTop() - top.bottomSidePositionFromTop());
+ }
+ }
+
+ @Override
+ public void updateFields() {
+ super.updateFields();
+ if (split != null)
+ split.setEnabled(((FinSet) component).getFinCount() > 1);
+ }
+
+ /**
+ * A container class to store pertinent info about centering rings. This is used in the computation to figure
+ * out tab length and position.
+ */
+ static class SortableRing {
+
+ /**
+ * The length of the ring (more commonly called the thickness).
+ */
+ private double thickness;
+ /**
+ * The position of the ring from the top of the parent.
+ */
+ private double positionFromTop;
+
+ /**
+ * Constructor.
+ *
+ * @param r the source centering ring
+ */
+ SortableRing(CenteringRing r) {
+ thickness = r.getLength();
+ positionFromTop = r.asPositionValue(RocketComponent.Position.TOP);
+ }
+
+ /**
+ * Merge an adjacent ring.
+ *
+ * @param adjacent the adjacent ring
+ */
+ public void merge(CenteringRing adjacent) {
+ double v = adjacent.asPositionValue(RocketComponent.Position.TOP);
+ if (positionFromTop < v) {
+ thickness = (v + adjacent.getLength()) - positionFromTop;
+ } else {
+ double tmp = positionFromTop + thickness;
+ positionFromTop = v;
+ thickness = tmp - v;
+ }
+ }
+
+ /**
+ * Compute the position of the bottom edge of the ring, relative to the top of the parent.
+ *
+ * @return the distance from the top of the parent to the bottom edge of the ring
+ */
+ public double bottomSidePositionFromTop() {
+ return positionFromTop + thickness;
+ }
+
+ /**
+ * Compute the position of the top edge of the ring, relative to the top of the parent.
+ *
+ * @return the distance from the top of the parent to the top edge of the ring
+ */
+ public double positionFromTop() {
+ return positionFromTop;
+ }
+ }
}
--- /dev/null
+package net.sf.openrocket.gui.configdialog;
+
+import net.sf.openrocket.gui.adaptors.DoubleModel;
+import net.sf.openrocket.rocketcomponent.CenteringRing;
+import org.junit.Assert;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.List;
+
+public class FinSetConfigTest {
+
+ static Method method;
+
+ @BeforeClass
+ public static void classSetup() throws Exception {
+ method = FinSetConfig.class.getDeclaredMethod("computeFinTabLength", List.class, Double.class, Double.class, DoubleModel.class);
+ Assert.assertNotNull(method);
+ method.setAccessible(true);
+ }
+
+ /**
+ * Test no centering rings.
+ *
+ * @throws Exception
+ */
+ @Test
+ public void testComputeFinTabLength() throws Exception {
+ DoubleModel dm = new DoubleModel(1d);
+ List<CenteringRing> rings = new ArrayList<CenteringRing>();
+
+ Double result = (Double)method.invoke(null, rings, 10d, 11d, dm);
+ Assert.assertEquals(0.0001, 11d, result.doubleValue());
+ result = (Double)method.invoke(null, null, 10d, 11d, dm);
+ Assert.assertEquals(11d, result.doubleValue(), 0.0001);
+ }
+
+ /**
+ * Test 2 rings both ahead of the fin.
+ */
+ @Test
+ public void testCompute2LeadingRings() throws Exception {
+ DoubleModel dm = new DoubleModel(1d);
+ List<CenteringRing> rings = new ArrayList<CenteringRing>();
+
+ CenteringRing ring1 = new CenteringRing();
+ ring1.setLength(0.004);
+ ring1.setPositionValue(0.43);
+ CenteringRing ring2 = new CenteringRing();
+ ring2.setLength(0.004);
+ ring2.setPositionValue(0.45);
+ rings.add(ring1);
+ rings.add(ring2);
+
+ Double result = (Double)method.invoke(null, rings, 0.47d, 0.01, dm);
+ Assert.assertEquals(0.01, result.doubleValue(), 0.0001);
+
+ }
+
+ /**
+ * Test one ring, ahead of the fin.
+ */
+ @Test
+ public void testCompute1Ring() throws Exception {
+ DoubleModel dm = new DoubleModel(1d);
+ List<CenteringRing> rings = new ArrayList<CenteringRing>();
+
+ CenteringRing ring1 = new CenteringRing();
+ ring1.setLength(0.004);
+ ring1.setPositionValue(0.43);
+ rings.add(ring1);
+
+ Double result = (Double)method.invoke(null, rings, 0.47d, 0.01, dm);
+ Assert.assertEquals(0.01, result.doubleValue(), 0.0001);
+ }
+
+ /**
+ * Test one ring ahead of the fin, one ring within the root chord.
+ */
+ @Test
+ public void testComputeOneLeadingOneRingWithinRoot() throws Exception {
+ DoubleModel dm = new DoubleModel(1d);
+ List<CenteringRing> rings = new ArrayList<CenteringRing>();
+
+ CenteringRing ring1 = new CenteringRing();
+ ring1.setLength(0.004);
+ ring1.setPositionValue(0.43);
+ CenteringRing ring2 = new CenteringRing();
+ ring2.setLength(0.004);
+ ring2.setPositionValue(0.45);
+ rings.add(ring1);
+ rings.add(ring2);
+
+ Double result = (Double)method.invoke(null, rings, 0.45d, 0.01, dm);
+ Assert.assertEquals(0.01 - 0.004, result.doubleValue(), 0.0001);
+ }
+
+ /**
+ * Test one ring ahead of the fin, one ring beyond the root chord.
+ */
+ @Test
+ public void testComputeOneLeadingOneTrailingRing() throws Exception {
+ DoubleModel dm = new DoubleModel(1d);
+ List<CenteringRing> rings = new ArrayList<CenteringRing>();
+
+ CenteringRing ring1 = new CenteringRing();
+ ring1.setLength(0.004);
+ ring1.setPositionValue(0.43);
+ CenteringRing ring2 = new CenteringRing();
+ ring2.setLength(0.004);
+ ring2.setPositionValue(0.48);
+ rings.add(ring1);
+ rings.add(ring2);
+
+ Double result = (Double)method.invoke(null, rings, 0.47d, 0.01, dm);
+ Assert.assertEquals(0.01, result.doubleValue(), 0.0001);
+ }
+
+ /**
+ * Test one ring within the root chord, another ring beyond the root chord.
+ */
+ @Test
+ public void testComputeOneWithinRootOneTrailingRing() throws Exception {
+ DoubleModel dm = new DoubleModel(1d);
+ List<CenteringRing> rings = new ArrayList<CenteringRing>();
+
+ CenteringRing ring1 = new CenteringRing();
+ ring1.setLength(0.004);
+ ring1.setPositionValue(0.4701);
+ CenteringRing ring2 = new CenteringRing();
+ ring2.setLength(0.004);
+ ring2.setPositionValue(0.48);
+ rings.add(ring1);
+ rings.add(ring2);
+
+ Double result = (Double)method.invoke(null, rings, 0.47d, 0.01, dm);
+ Assert.assertEquals(0.0059, result.doubleValue(), 0.0001);
+ }
+
+ /**
+ * Test both rings within the root chord.
+ */
+ @Test
+ public void testBothRingsWithinRootChord() throws Exception {
+ DoubleModel dm = new DoubleModel(1d);
+ List<CenteringRing> rings = new ArrayList<CenteringRing>();
+
+ CenteringRing ring1 = new CenteringRing();
+ ring1.setLength(0.004);
+ ring1.setPositionValue(0.4701);
+ CenteringRing ring2 = new CenteringRing();
+ ring2.setLength(0.004);
+ ring2.setPositionValue(0.4750);
+ rings.add(ring1);
+ rings.add(ring2);
+
+ Double result = (Double)method.invoke(null, rings, 0.47d, 0.01, dm);
+ Assert.assertEquals(0.0009, result.doubleValue(), 0.0001);
+ }
+
+
+ /**
+ * Test both rings beyond the root chord.
+ */
+ @Test
+ public void testBothRingsBeyondRootChord() throws Exception {
+ DoubleModel dm = new DoubleModel(1d);
+ List<CenteringRing> rings = new ArrayList<CenteringRing>();
+
+ CenteringRing ring1 = new CenteringRing();
+ ring1.setLength(0.004);
+ ring1.setPositionValue(0.48);
+ CenteringRing ring2 = new CenteringRing();
+ ring2.setLength(0.004);
+ ring2.setPositionValue(0.49);
+ rings.add(ring1);
+ rings.add(ring2);
+
+ Double result = (Double)method.invoke(null, rings, 0.47d, 0.01, dm);
+ Assert.assertEquals(0.006, result.doubleValue(), 0.0001);
+ }
+
+ /**
+ * Test both rings within the root chord - the top ring has an adjacent ring (so 3 rings total).
+ */
+ @Test
+ public void test3RingsWithinRootChord() throws Exception {
+ DoubleModel dm = new DoubleModel(1d);
+ List<CenteringRing> rings = new ArrayList<CenteringRing>();
+
+ CenteringRing ring1 = new CenteringRing();
+ ring1.setLength(0.004);
+ ring1.setPositionValue(0.47);
+ CenteringRing ring2 = new CenteringRing();
+ ring2.setLength(0.004);
+ ring2.setPositionValue(0.4702);
+ CenteringRing ring3 = new CenteringRing();
+ ring3.setLength(0.004);
+ ring3.setPositionValue(0.4770);
+ rings.add(ring1);
+ rings.add(ring2);
+ rings.add(ring3);
+
+ Double result = (Double)method.invoke(null, rings, 0.47d, 0.01, dm);
+ Assert.assertEquals(0.0028, result.doubleValue(), 0.0001);
+ }
+
+}