Merge branch 'tumble-recovery' into kruland-integration

Conflicts:
	core/resources/datafiles/examples/Three-stage rocket.ork
	core/src/net/sf/openrocket/simulation/BasicEventSimulationEngine.java

Ignored changes to Three-stage rocket.ork since that will need to be
reproduced after the cool 3d view enhancements to the example.

The BasicEventSimulationEngine conflicts were trivial and manually
resolved.
This commit is contained in:
kruland2607 2013-03-13 14:34:11 -05:00
commit 3eb96ee04d
7 changed files with 270 additions and 1 deletions

View File

@ -1371,6 +1371,7 @@ FlightEvent.Type.RECOVERY_DEVICE_DEPLOYMENT = Recovery device deployment
FlightEvent.Type.GROUND_HIT = Ground hit FlightEvent.Type.GROUND_HIT = Ground hit
FlightEvent.Type.SIMULATION_END = Simulation end FlightEvent.Type.SIMULATION_END = Simulation end
FlightEvent.Type.ALTITUDE = Altitude change FlightEvent.Type.ALTITUDE = Altitude change
FlightEvent.Type.TUMBLE = Tumbling
! ThrustCurveMotorColumns ! ThrustCurveMotorColumns
TCurveMotorCol.MANUFACTURER = Manufacturer TCurveMotorCol.MANUFACTURER = Manufacturer

View File

@ -36,6 +36,7 @@ public class PlotConfiguration implements Cloneable {
config.setEvent(FlightEvent.Type.RECOVERY_DEVICE_DEPLOYMENT, true); config.setEvent(FlightEvent.Type.RECOVERY_DEVICE_DEPLOYMENT, true);
config.setEvent(FlightEvent.Type.STAGE_SEPARATION, true); config.setEvent(FlightEvent.Type.STAGE_SEPARATION, true);
config.setEvent(FlightEvent.Type.GROUND_HIT, true); config.setEvent(FlightEvent.Type.GROUND_HIT, true);
config.setEvent(FlightEvent.Type.TUMBLE, true);
configs.add(config); configs.add(config);
//// Total motion vs. time //// Total motion vs. time
@ -49,6 +50,7 @@ public class PlotConfiguration implements Cloneable {
config.setEvent(FlightEvent.Type.RECOVERY_DEVICE_DEPLOYMENT, true); config.setEvent(FlightEvent.Type.RECOVERY_DEVICE_DEPLOYMENT, true);
config.setEvent(FlightEvent.Type.STAGE_SEPARATION, true); config.setEvent(FlightEvent.Type.STAGE_SEPARATION, true);
config.setEvent(FlightEvent.Type.GROUND_HIT, true); config.setEvent(FlightEvent.Type.GROUND_HIT, true);
config.setEvent(FlightEvent.Type.TUMBLE, true);
configs.add(config); configs.add(config);
//// Flight side profile //// Flight side profile
@ -60,6 +62,7 @@ public class PlotConfiguration implements Cloneable {
config.setEvent(FlightEvent.Type.RECOVERY_DEVICE_DEPLOYMENT, true); config.setEvent(FlightEvent.Type.RECOVERY_DEVICE_DEPLOYMENT, true);
config.setEvent(FlightEvent.Type.STAGE_SEPARATION, true); config.setEvent(FlightEvent.Type.STAGE_SEPARATION, true);
config.setEvent(FlightEvent.Type.GROUND_HIT, true); config.setEvent(FlightEvent.Type.GROUND_HIT, true);
config.setEvent(FlightEvent.Type.TUMBLE, true);
configs.add(config); configs.add(config);
//// Stability vs. time //// Stability vs. time
@ -73,6 +76,7 @@ public class PlotConfiguration implements Cloneable {
config.setEvent(FlightEvent.Type.RECOVERY_DEVICE_DEPLOYMENT, true); config.setEvent(FlightEvent.Type.RECOVERY_DEVICE_DEPLOYMENT, true);
config.setEvent(FlightEvent.Type.STAGE_SEPARATION, true); config.setEvent(FlightEvent.Type.STAGE_SEPARATION, true);
config.setEvent(FlightEvent.Type.GROUND_HIT, true); config.setEvent(FlightEvent.Type.GROUND_HIT, true);
config.setEvent(FlightEvent.Type.TUMBLE, true);
configs.add(config); configs.add(config);
//// Drag coefficients vs. Mach number //// Drag coefficients vs. Mach number
@ -97,6 +101,7 @@ public class PlotConfiguration implements Cloneable {
config.setEvent(FlightEvent.Type.RECOVERY_DEVICE_DEPLOYMENT, true); config.setEvent(FlightEvent.Type.RECOVERY_DEVICE_DEPLOYMENT, true);
config.setEvent(FlightEvent.Type.STAGE_SEPARATION, true); config.setEvent(FlightEvent.Type.STAGE_SEPARATION, true);
config.setEvent(FlightEvent.Type.GROUND_HIT, true); config.setEvent(FlightEvent.Type.GROUND_HIT, true);
config.setEvent(FlightEvent.Type.TUMBLE, true);
configs.add(config); configs.add(config);
//// Angle of attack and orientation vs. time //// Angle of attack and orientation vs. time
@ -110,6 +115,7 @@ public class PlotConfiguration implements Cloneable {
config.setEvent(FlightEvent.Type.RECOVERY_DEVICE_DEPLOYMENT, true); config.setEvent(FlightEvent.Type.RECOVERY_DEVICE_DEPLOYMENT, true);
config.setEvent(FlightEvent.Type.STAGE_SEPARATION, true); config.setEvent(FlightEvent.Type.STAGE_SEPARATION, true);
config.setEvent(FlightEvent.Type.GROUND_HIT, true); config.setEvent(FlightEvent.Type.GROUND_HIT, true);
config.setEvent(FlightEvent.Type.TUMBLE, true);
configs.add(config); configs.add(config);
//// Simulation time step and computation time //// Simulation time step and computation time
@ -122,6 +128,7 @@ public class PlotConfiguration implements Cloneable {
config.setEvent(FlightEvent.Type.RECOVERY_DEVICE_DEPLOYMENT, true); config.setEvent(FlightEvent.Type.RECOVERY_DEVICE_DEPLOYMENT, true);
config.setEvent(FlightEvent.Type.STAGE_SEPARATION, true); config.setEvent(FlightEvent.Type.STAGE_SEPARATION, true);
config.setEvent(FlightEvent.Type.GROUND_HIT, true); config.setEvent(FlightEvent.Type.GROUND_HIT, true);
config.setEvent(FlightEvent.Type.TUMBLE, true);
configs.add(config); configs.add(config);
DEFAULT_CONFIGURATIONS = configs.toArray(new PlotConfiguration[0]); DEFAULT_CONFIGURATIONS = configs.toArray(new PlotConfiguration[0]);

View File

@ -37,6 +37,11 @@ public class BasicEventSimulationEngine implements SimulationEngine {
// TODO: MEDIUM: Allow selecting steppers // TODO: MEDIUM: Allow selecting steppers
private SimulationStepper flightStepper = new RK4SimulationStepper(); private SimulationStepper flightStepper = new RK4SimulationStepper();
private SimulationStepper landingStepper = new BasicLandingStepper(); private SimulationStepper landingStepper = new BasicLandingStepper();
private SimulationStepper tumbleStepper = new BasicTumbleStepper();
// Constant holding 30 degress in radians. This is the AOA condition
// necessary to transistion to tumbling.
private final static double AOA_TUMBLE_CONDITION = Math.PI / 3.0;
private SimulationStepper currentStepper; private SimulationStepper currentStepper;
@ -192,6 +197,24 @@ public class BasicEventSimulationEngine implements SimulationEngine {
} }
} }
// Check for Tumbling
// Conditions for transision are:
// apogee reached
// and is not already tumbling
// and not stable (cg > cp)
// and aoa > 30
if (status.isApogeeReached() && !status.isTumbling()) {
double cp = status.getFlightData().getLast(FlightDataType.TYPE_CP_LOCATION);
double cg = status.getFlightData().getLast(FlightDataType.TYPE_CG_LOCATION);
double aoa = status.getFlightData().getLast(FlightDataType.TYPE_AOA);
if (cg > cp && aoa > AOA_TUMBLE_CONDITION) {
addEvent(new FlightEvent(FlightEvent.Type.TUMBLE, status.getSimulationTime()));
status.setTumbling(true);
}
}
} }
} catch (SimulationException e) { } catch (SimulationException e) {
@ -479,6 +502,12 @@ public class BasicEventSimulationEngine implements SimulationEngine {
case ALTITUDE: case ALTITUDE:
break; break;
case TUMBLE:
this.currentStepper = this.tumbleStepper;
this.status = currentStepper.initialize(status);
status.getFlightData().addEvent(event);
break;
} }
} }

View File

@ -0,0 +1,77 @@
package net.sf.openrocket.simulation;
import java.util.Iterator;
import net.sf.openrocket.aerodynamics.AerodynamicForces;
import net.sf.openrocket.aerodynamics.FlightConditions;
import net.sf.openrocket.aerodynamics.WarningSet;
import net.sf.openrocket.motor.MotorInstanceConfiguration;
import net.sf.openrocket.rocketcomponent.Configuration;
import net.sf.openrocket.rocketcomponent.FinSet;
import net.sf.openrocket.rocketcomponent.Rocket;
import net.sf.openrocket.rocketcomponent.RocketComponent;
import net.sf.openrocket.rocketcomponent.SymmetricComponent;
public class BasicTumbleStatus extends SimulationStatus {
// Magic constants from techdoc.pdf
private final static double cDFin = 1.42;
private final static double cDBt = 0.56;
// Fin efficiency. Index is number of fins. The 0th entry is arbitrary and used to
// offset the indexes so finEff[1] is the coefficient for one fin from the table in techdoc.pdf
private final static double[] finEff = { 0.0, 0.5, 1.0, 1.41, 1.81, 1.73, 1.90, 1.85 };
private double drag;
public BasicTumbleStatus(Configuration configuration,
MotorInstanceConfiguration motorConfiguration,
SimulationConditions simulationConditions) {
super(configuration, motorConfiguration, simulationConditions);
computeTumbleDrag();
}
public BasicTumbleStatus(SimulationStatus orig) {
super(orig);
if ( orig instanceof BasicTumbleStatus ) {
this.drag = ((BasicTumbleStatus) orig).drag;
}
}
public double getTumbleDrag( ) {
return drag;
}
public void computeTumbleDrag() {
// Computed based on Sampo's experimentation as documented in the pdf.
// compute the fin and body tube projected areas
double aFins = 0.0;
double aBt = 0.0;
Rocket r = this.getConfiguration().getRocket();
Iterator<RocketComponent> componentIterator = r.iterator();
while ( componentIterator.hasNext() ) {
RocketComponent component = componentIterator.next();
if ( ! component.isAerodynamic() ) {
continue;
}
if ( component instanceof FinSet ) {
double finComponent = ((FinSet)component).getFinArea();
int finCount = ((FinSet)component).getFinCount();
// check bounds on finCount.
if ( finCount >= finEff.length ) {
finCount = finEff.length-1;
}
aFins += finComponent * finEff[finCount];
} else if (component instanceof SymmetricComponent ){
aBt += ((SymmetricComponent) component) .getComponentPlanformArea();
}
}
drag = ( cDFin * aFins + cDBt * aBt );
}
}

View File

@ -0,0 +1,137 @@
package net.sf.openrocket.simulation;
import net.sf.openrocket.models.atmosphere.AtmosphericConditions;
import net.sf.openrocket.simulation.exception.SimulationException;
import net.sf.openrocket.util.Coordinate;
import net.sf.openrocket.util.GeodeticComputationStrategy;
import net.sf.openrocket.util.MathUtil;
import net.sf.openrocket.util.WorldCoordinate;
public class BasicTumbleStepper extends AbstractSimulationStepper {
private static final double RECOVERY_TIME_STEP = 0.5;
@Override
public SimulationStatus initialize(SimulationStatus status) throws SimulationException {
return new BasicTumbleStatus(status);
}
@Override
public void step(SimulationStatus status, double maxTimeStep) throws SimulationException {
// Get the atmospheric conditions
AtmosphericConditions atmosphere = modelAtmosphericConditions(status);
//// Local wind speed and direction
Coordinate windSpeed = modelWindVelocity(status);
Coordinate airSpeed = status.getRocketVelocity().add(windSpeed);
// Get total CD
double mach = airSpeed.length() / atmosphere.getMachSpeed();
double tumbleDrag = ((BasicTumbleStatus)status).getTumbleDrag();
// Compute drag force
double dynP = (0.5 * atmosphere.getDensity() * airSpeed.length2());
double dragForce = tumbleDrag * dynP;
MassData massData = calculateMassData(status);
double mass = massData.getCG().weight;
// Compute drag acceleration
Coordinate linearAcceleration;
if (airSpeed.length() > 0.001) {
linearAcceleration = airSpeed.normalize().multiply(-dragForce / mass);
} else {
linearAcceleration = Coordinate.NUL;
}
// Add effect of gravity
double gravity = modelGravity(status);
linearAcceleration = linearAcceleration.sub(0, 0, gravity);
// Add coriolis acceleration
Coordinate coriolisAcceleration = status.getSimulationConditions().getGeodeticComputation().getCoriolisAcceleration(
status.getRocketWorldPosition(), status.getRocketVelocity());
linearAcceleration = linearAcceleration.add(coriolisAcceleration);
// Select time step
double timeStep = MathUtil.min(0.5 / linearAcceleration.length(), RECOVERY_TIME_STEP);
// Perform Euler integration
status.setRocketPosition(status.getRocketPosition().add(status.getRocketVelocity().multiply(timeStep)).
add(linearAcceleration.multiply(MathUtil.pow2(timeStep) / 2)));
status.setRocketVelocity(status.getRocketVelocity().add(linearAcceleration.multiply(timeStep)));
status.setSimulationTime(status.getSimulationTime() + timeStep);
// Update the world coordinate
WorldCoordinate w = status.getSimulationConditions().getLaunchSite();
w = status.getSimulationConditions().getGeodeticComputation().addCoordinate(w, status.getRocketPosition());
status.setRocketWorldPosition(w);
// Store data
FlightDataBranch data = status.getFlightData();
boolean extra = status.getSimulationConditions().isCalculateExtras();
data.addPoint();
data.setValue(FlightDataType.TYPE_TIME, status.getSimulationTime());
data.setValue(FlightDataType.TYPE_ALTITUDE, status.getRocketPosition().z);
data.setValue(FlightDataType.TYPE_POSITION_X, status.getRocketPosition().x);
data.setValue(FlightDataType.TYPE_POSITION_Y, status.getRocketPosition().y);
if (extra) {
data.setValue(FlightDataType.TYPE_POSITION_XY,
MathUtil.hypot(status.getRocketPosition().x, status.getRocketPosition().y));
data.setValue(FlightDataType.TYPE_POSITION_DIRECTION,
Math.atan2(status.getRocketPosition().y, status.getRocketPosition().x));
data.setValue(FlightDataType.TYPE_VELOCITY_XY,
MathUtil.hypot(status.getRocketVelocity().x, status.getRocketVelocity().y));
data.setValue(FlightDataType.TYPE_ACCELERATION_XY,
MathUtil.hypot(linearAcceleration.x, linearAcceleration.y));
data.setValue(FlightDataType.TYPE_ACCELERATION_TOTAL, linearAcceleration.length());
double Re = airSpeed.length() *
status.getConfiguration().getLength() /
atmosphere.getKinematicViscosity();
data.setValue(FlightDataType.TYPE_REYNOLDS_NUMBER, Re);
}
data.setValue(FlightDataType.TYPE_LATITUDE, status.getRocketWorldPosition().getLatitudeRad());
data.setValue(FlightDataType.TYPE_LONGITUDE, status.getRocketWorldPosition().getLongitudeRad());
data.setValue(FlightDataType.TYPE_GRAVITY, gravity);
if (status.getSimulationConditions().getGeodeticComputation() != GeodeticComputationStrategy.FLAT) {
data.setValue(FlightDataType.TYPE_CORIOLIS_ACCELERATION, coriolisAcceleration.length());
}
data.setValue(FlightDataType.TYPE_VELOCITY_Z, status.getRocketVelocity().z);
data.setValue(FlightDataType.TYPE_ACCELERATION_Z, linearAcceleration.z);
data.setValue(FlightDataType.TYPE_VELOCITY_TOTAL, airSpeed.length());
data.setValue(FlightDataType.TYPE_MACH_NUMBER, mach);
data.setValue(FlightDataType.TYPE_MASS, mass);
data.setValue(FlightDataType.TYPE_PROPELLANT_MASS, 0.0); // Is this a reasonable assumption? Probably.
data.setValue(FlightDataType.TYPE_THRUST_FORCE, 0);
data.setValue(FlightDataType.TYPE_DRAG_FORCE, dragForce);
data.setValue(FlightDataType.TYPE_WIND_VELOCITY, windSpeed.length());
data.setValue(FlightDataType.TYPE_AIR_TEMPERATURE, atmosphere.getTemperature());
data.setValue(FlightDataType.TYPE_AIR_PRESSURE, atmosphere.getPressure());
data.setValue(FlightDataType.TYPE_SPEED_OF_SOUND, atmosphere.getMachSpeed());
data.setValue(FlightDataType.TYPE_TIME_STEP, timeStep);
data.setValue(FlightDataType.TYPE_COMPUTATION_TIME,
(System.nanoTime() - status.getSimulationStartWallTime()) / 1000000000.0);
}
}

View File

@ -72,7 +72,12 @@ public class FlightEvent implements Comparable<FlightEvent> {
* A change in altitude has occurred. Data is a <code>Pair<Double,Double></code> * A change in altitude has occurred. Data is a <code>Pair<Double,Double></code>
* which contains the old and new altitudes. * which contains the old and new altitudes.
*/ */
ALTITUDE(trans.get("FlightEvent.Type.ALTITUDE")); ALTITUDE(trans.get("FlightEvent.Type.ALTITUDE")),
/**
* The rocket begins to tumble.
*/
TUMBLE(trans.get("FlightEvent.Type.TUMBLE"));
private final String name; private final String name;

View File

@ -69,6 +69,9 @@ public class SimulationStatus implements Monitorable {
/** Set to true when apogee has been detected. */ /** Set to true when apogee has been detected. */
private boolean apogeeReached = false; private boolean apogeeReached = false;
/** Set to true to indicate the rocket is tumbling. */
private boolean tumbling = false;
/** Contains a list of deployed recovery devices. */ /** Contains a list of deployed recovery devices. */
private MonitorableSet<RecoveryDevice> deployedRecoveryDevices = new MonitorableSet<RecoveryDevice>(); private MonitorableSet<RecoveryDevice> deployedRecoveryDevices = new MonitorableSet<RecoveryDevice>();
@ -179,6 +182,7 @@ public class SimulationStatus implements Monitorable {
this.liftoff = orig.liftoff; this.liftoff = orig.liftoff;
this.launchRodCleared = orig.launchRodCleared; this.launchRodCleared = orig.launchRodCleared;
this.apogeeReached = orig.apogeeReached; this.apogeeReached = orig.apogeeReached;
this.tumbling = orig.tumbling;
this.motorBurntOut = orig.motorBurntOut; this.motorBurntOut = orig.motorBurntOut;
this.deployedRecoveryDevices.clear(); this.deployedRecoveryDevices.clear();
@ -380,6 +384,15 @@ public class SimulationStatus implements Monitorable {
} }
public void setTumbling( boolean tumbling ) {
this.tumbling = tumbling;
this.modID++;
}
public boolean isTumbling() {
return tumbling;
}
public Set<RecoveryDevice> getDeployedRecoveryDevices() { public Set<RecoveryDevice> getDeployedRecoveryDevices() {
return deployedRecoveryDevices; return deployedRecoveryDevices;
} }