diff --git a/core/src/net/sf/openrocket/file/openrocket/importt/OpenRocketLoader.java b/core/src/net/sf/openrocket/file/openrocket/importt/OpenRocketLoader.java index 4efdf9dbd..0915a9353 100644 --- a/core/src/net/sf/openrocket/file/openrocket/importt/OpenRocketLoader.java +++ b/core/src/net/sf/openrocket/file/openrocket/importt/OpenRocketLoader.java @@ -1494,7 +1494,7 @@ class SimulationConditionsHandler extends AbstractElementHandler { } else if (element.equals("atmosphere")) { atmosphereHandler.storeSettings(conditions, warnings); } else if (element.equals("timestep")) { - if (Double.isNaN(d)) { + if (Double.isNaN(d) || d <= 0 ) { warnings.add("Illegal time step defined, ignoring."); } else { conditions.setTimeStep(d); diff --git a/core/src/net/sf/openrocket/rocketcomponent/FinSet.java b/core/src/net/sf/openrocket/rocketcomponent/FinSet.java index 4b8d58058..40aac2f90 100644 --- a/core/src/net/sf/openrocket/rocketcomponent/FinSet.java +++ b/core/src/net/sf/openrocket/rocketcomponent/FinSet.java @@ -440,7 +440,7 @@ public abstract class FinSet extends ExternalComponent { double da = (y0 + y1) * (x1 - x0) / 2; finArea += da; - if (Math.abs(y0 - y1) < 0.00001) { + if (Math.abs(y0 + y1) < 0.00001) { finCGx += (x0 + x1) / 2 * da; finCGy += y0 / 2 * da; } else { diff --git a/core/src/net/sf/openrocket/simulation/RK4SimulationStepper.java b/core/src/net/sf/openrocket/simulation/RK4SimulationStepper.java index 8548ec91e..fdc4ba626 100644 --- a/core/src/net/sf/openrocket/simulation/RK4SimulationStepper.java +++ b/core/src/net/sf/openrocket/simulation/RK4SimulationStepper.java @@ -138,8 +138,9 @@ public class RK4SimulationStepper extends AbstractSimulationStepper { */ double[] dt = new double[8]; Arrays.fill(dt, Double.MAX_VALUE); - - dt[0] = status.getSimulationConditions().getTimeStep(); + + // If the user selected a really small timestep, use MIN_TIME_STEP instead. + dt[0] = MathUtil.max(status.getSimulationConditions().getTimeStep(),MIN_TIME_STEP); dt[1] = maxTimeStep; dt[2] = status.getSimulationConditions().getMaximumAngleStep() / store.lateralPitchRate; dt[3] = Math.abs(MAX_ROLL_STEP_ANGLE / store.flightConditions.getRollRate()); @@ -159,7 +160,7 @@ public class RK4SimulationStepper extends AbstractSimulationStepper { limitingValue = i; } } - + double minTimeStep = status.getSimulationConditions().getTimeStep() / 20; if (store.timestep < minTimeStep) { log.verbose("Too small time step " + store.timestep + " (limiting factor " + limitingValue + "), using " + diff --git a/core/test/net/sf/openrocket/rocketcomponent/FinSetTest.java b/core/test/net/sf/openrocket/rocketcomponent/FinSetTest.java index 321efbe8c..14299440c 100644 --- a/core/test/net/sf/openrocket/rocketcomponent/FinSetTest.java +++ b/core/test/net/sf/openrocket/rocketcomponent/FinSetTest.java @@ -1,6 +1,8 @@ package net.sf.openrocket.rocketcomponent; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; import java.awt.Color; @@ -11,26 +13,172 @@ import net.sf.openrocket.rocketcomponent.ExternalComponent.Finish; import net.sf.openrocket.rocketcomponent.FinSet.CrossSection; import net.sf.openrocket.rocketcomponent.FinSet.TabRelativePosition; import net.sf.openrocket.rocketcomponent.RocketComponent.Position; +import net.sf.openrocket.util.Coordinate; import net.sf.openrocket.util.LineStyle; import net.sf.openrocket.util.BaseTestCase.BaseTestCase; import org.junit.Test; public class FinSetTest extends BaseTestCase { - - + + @Test + public void testTrapezoidCGComputation() { + + { + // This is a simple square fin with sides of 1.0. + TrapezoidFinSet fins = new TrapezoidFinSet(); + fins.setFinCount(1); + fins.setFinShape(1.0, 1.0, 0.0, 1.0, .005); + + Coordinate coords = fins.getCG(); + assertEquals(1.0, fins.getFinArea(), 0.001); + assertEquals(0.5, coords.x, 0.001); + assertEquals(0.5, coords.y, 0.001); + } + + { + // This is a trapezoid. Height 1, root 1, tip 1/2 no sweep. + // It can be decomposed into a rectangle followed by a triangle + // +---+ + // | \ + // | \ + // +------+ + TrapezoidFinSet fins = new TrapezoidFinSet(); + fins.setFinCount(1); + fins.setFinShape(1.0, 0.5, 0.0, 1.0, .005); + + Coordinate coords = fins.getCG(); + assertEquals(0.75, fins.getFinArea(), 0.001); + assertEquals(0.3889, coords.x, 0.001); + assertEquals(0.4444, coords.y, 0.001); + } + + } + + @Test + public void testFreeformCGComputation() throws Exception { + + { + // This is a trapezoid. Height 1, root 1, tip 1/2 no sweep. + // It can be decomposed into a rectangle followed by a triangle + // +---+ + // | \ + // | \ + // +------+ + FreeformFinSet fins = new FreeformFinSet(); + fins.setFinCount(1); + Coordinate[] points = new Coordinate[] { + new Coordinate(0,0), + new Coordinate(0,1), + new Coordinate(.5,1), + new Coordinate(1,0) + }; + fins.setPoints(points); + Coordinate coords = fins.getCG(); + assertEquals(0.75, fins.getFinArea(), 0.001); + assertEquals(0.3889, coords.x, 0.001); + assertEquals(0.4444, coords.y, 0.001); + } + + { + // This is the same trapezoid as previous free form, but it has + // some extra points along the lines. + FreeformFinSet fins = new FreeformFinSet(); + fins.setFinCount(1); + Coordinate[] points = new Coordinate[] { + new Coordinate(0,0), + new Coordinate(0,.5), + new Coordinate(0,1), + new Coordinate(.25,1), + new Coordinate(.5,1), + new Coordinate(.75,.5), + new Coordinate(1,0) + }; + fins.setPoints(points); + Coordinate coords = fins.getCG(); + assertEquals(0.75, fins.getFinArea(), 0.001); + assertEquals(0.3889, coords.x, 0.001); + assertEquals(0.4444, coords.y, 0.001); + } + + { + // This is the same trapezoid as previous free form, but it has + // some extra points which are very close to previous points. + // in particular for points 0 & 1, + // y0 + y1 is very small. + FreeformFinSet fins = new FreeformFinSet(); + fins.setFinCount(1); + Coordinate[] points = new Coordinate[] { + new Coordinate(0,0), + new Coordinate(0,1E-15), + new Coordinate(0,1), + new Coordinate(1E-15,1), + new Coordinate(.5,1), + new Coordinate(.5,1-1E-15), + new Coordinate(1,1E-15), + new Coordinate(1,0) + }; + fins.setPoints(points); + Coordinate coords = fins.getCG(); + assertEquals(0.75, fins.getFinArea(), 0.001); + assertEquals(0.3889, coords.x, 0.001); + assertEquals(0.4444, coords.y, 0.001); + } + + } + + @Test + public void testFreeFormCGWithNegativeY() throws Exception { + // This particular fin shape is currently not allowed in OR since the y values are negative + // however, it is possible to convert RockSim files and end up with fins which + // have negative y values. + + // A user submitted an ork file which could not be simulated because the fin + // was constructed on a tail cone. It so happened that for one pair of points + // y_n = - y_(n+1) which caused a divide by zero and resulted in CGx = NaN. + + // This Fin set is constructed to have the same problem. It is a square and rectagle + // where the two trailing edge corners of the rectangle satisfy y_0 = -y_1 + // + // +---------+ + // | | + // | | + // +----+ | + // | | + // | | + // +----+ + + FreeformFinSet fins = new FreeformFinSet(); + fins.setFinCount(1); + Coordinate[] points = new Coordinate[] { + new Coordinate(0,0), + new Coordinate(0,1), + new Coordinate(2,1), + new Coordinate(2,-1), + new Coordinate(1,-1), + new Coordinate(1,0) + }; + fins.setPoints(points); + Coordinate coords = fins.getCG(); + assertEquals(3.0, fins.getFinArea(), 0.001); + assertEquals(3.5/3.0, coords.x, 0.001); + assertEquals(0.5/3.0, coords.y, 0.001); + + } + + @Test public void testFreeformConvert() { testFreeformConvert(new TrapezoidFinSet()); testFreeformConvert(new EllipticalFinSet()); testFreeformConvert(new FreeformFinSet()); } - - + + private void testFreeformConvert(FinSet fin) { FreeformFinSet converted; Material mat = Material.newMaterial(Type.BULK, "foo", 0.1, true); - + fin.setBaseRotation(1.1); fin.setCantAngle(0.001); fin.setCGOverridden(true); @@ -52,57 +200,57 @@ public class FinSetTest extends BaseTestCase { fin.setTabRelativePosition(TabRelativePosition.END); fin.setTabShift(0.015); fin.setThickness(0.005); - - + + converted = FreeformFinSet.convertFinSet((FinSet) fin.copy()); - + ComponentCompare.assertSimilarity(fin, converted, true); - + assertEquals(converted.getComponentName(), converted.getName()); - - + + // Create test rocket Rocket rocket = new Rocket(); Stage stage = new Stage(); BodyTube body = new BodyTube(); - + rocket.addChild(stage); stage.addChild(body); body.addChild(fin); - + Listener l1 = new Listener("l1"); rocket.addComponentChangeListener(l1); - + fin.setName("Custom name"); assertTrue(l1.changed); assertEquals(ComponentChangeEvent.NONFUNCTIONAL_CHANGE, l1.changetype); - - + + // Create copy RocketComponent rocketcopy = rocket.copy(); - + Listener l2 = new Listener("l2"); rocketcopy.addComponentChangeListener(l2); - + FinSet fincopy = (FinSet) rocketcopy.getChild(0).getChild(0).getChild(0); FreeformFinSet.convertFinSet(fincopy); - + assertTrue(l2.changed); assertEquals(ComponentChangeEvent.TREE_CHANGE, l2.changetype & ComponentChangeEvent.TREE_CHANGE); - + } - - + + private static class Listener implements ComponentChangeListener { private boolean changed = false; private int changetype = 0; private final String name; - + public Listener(String name) { this.name = name; } - + @Override public void componentChanged(ComponentChangeEvent e) { assertFalse("Ensuring listener " + name + " has not been called.", changed); @@ -110,5 +258,5 @@ public class FinSetTest extends BaseTestCase { changetype = e.getType(); } } - + }