Added simulation of tumble recovery. Has pretty major FIXME which needs
addressing.
This commit is contained in:
parent
fab85c2918
commit
b99a704eb6
File diff suppressed because it is too large
Load Diff
@ -1368,6 +1368,7 @@ FlightEvent.Type.RECOVERY_DEVICE_DEPLOYMENT = Recovery device deployment
|
||||
FlightEvent.Type.GROUND_HIT = Ground hit
|
||||
FlightEvent.Type.SIMULATION_END = Simulation end
|
||||
FlightEvent.Type.ALTITUDE = Altitude change
|
||||
FlightEvent.Type.TUMBLE = Tumbling
|
||||
|
||||
! ThrustCurveMotorColumns
|
||||
TCurveMotorCol.MANUFACTURER = Manufacturer
|
||||
|
@ -37,6 +37,7 @@ public class PlotConfiguration implements Cloneable {
|
||||
config.setEvent(FlightEvent.Type.RECOVERY_DEVICE_DEPLOYMENT, true);
|
||||
config.setEvent(FlightEvent.Type.STAGE_SEPARATION, true);
|
||||
config.setEvent(FlightEvent.Type.GROUND_HIT, true);
|
||||
config.setEvent(FlightEvent.Type.TUMBLE, true);
|
||||
configs.add(config);
|
||||
|
||||
//// Total motion vs. time
|
||||
@ -50,6 +51,7 @@ public class PlotConfiguration implements Cloneable {
|
||||
config.setEvent(FlightEvent.Type.RECOVERY_DEVICE_DEPLOYMENT, true);
|
||||
config.setEvent(FlightEvent.Type.STAGE_SEPARATION, true);
|
||||
config.setEvent(FlightEvent.Type.GROUND_HIT, true);
|
||||
config.setEvent(FlightEvent.Type.TUMBLE, true);
|
||||
configs.add(config);
|
||||
|
||||
//// Flight side profile
|
||||
@ -61,6 +63,7 @@ public class PlotConfiguration implements Cloneable {
|
||||
config.setEvent(FlightEvent.Type.RECOVERY_DEVICE_DEPLOYMENT, true);
|
||||
config.setEvent(FlightEvent.Type.STAGE_SEPARATION, true);
|
||||
config.setEvent(FlightEvent.Type.GROUND_HIT, true);
|
||||
config.setEvent(FlightEvent.Type.TUMBLE, true);
|
||||
configs.add(config);
|
||||
|
||||
//// Stability vs. time
|
||||
@ -74,6 +77,7 @@ public class PlotConfiguration implements Cloneable {
|
||||
config.setEvent(FlightEvent.Type.RECOVERY_DEVICE_DEPLOYMENT, true);
|
||||
config.setEvent(FlightEvent.Type.STAGE_SEPARATION, true);
|
||||
config.setEvent(FlightEvent.Type.GROUND_HIT, true);
|
||||
config.setEvent(FlightEvent.Type.TUMBLE, true);
|
||||
configs.add(config);
|
||||
|
||||
//// Drag coefficients vs. Mach number
|
||||
@ -98,6 +102,7 @@ public class PlotConfiguration implements Cloneable {
|
||||
config.setEvent(FlightEvent.Type.RECOVERY_DEVICE_DEPLOYMENT, true);
|
||||
config.setEvent(FlightEvent.Type.STAGE_SEPARATION, true);
|
||||
config.setEvent(FlightEvent.Type.GROUND_HIT, true);
|
||||
config.setEvent(FlightEvent.Type.TUMBLE, true);
|
||||
configs.add(config);
|
||||
|
||||
//// Angle of attack and orientation vs. time
|
||||
@ -111,6 +116,7 @@ public class PlotConfiguration implements Cloneable {
|
||||
config.setEvent(FlightEvent.Type.RECOVERY_DEVICE_DEPLOYMENT, true);
|
||||
config.setEvent(FlightEvent.Type.STAGE_SEPARATION, true);
|
||||
config.setEvent(FlightEvent.Type.GROUND_HIT, true);
|
||||
config.setEvent(FlightEvent.Type.TUMBLE, true);
|
||||
configs.add(config);
|
||||
|
||||
//// Simulation time step and computation time
|
||||
@ -123,6 +129,7 @@ public class PlotConfiguration implements Cloneable {
|
||||
config.setEvent(FlightEvent.Type.RECOVERY_DEVICE_DEPLOYMENT, true);
|
||||
config.setEvent(FlightEvent.Type.STAGE_SEPARATION, true);
|
||||
config.setEvent(FlightEvent.Type.GROUND_HIT, true);
|
||||
config.setEvent(FlightEvent.Type.TUMBLE, true);
|
||||
configs.add(config);
|
||||
|
||||
DEFAULT_CONFIGURATIONS = configs.toArray(new PlotConfiguration[0]);
|
||||
|
@ -37,6 +37,7 @@ public class BasicEventSimulationEngine implements SimulationEngine {
|
||||
// TODO: MEDIUM: Allow selecting steppers
|
||||
private SimulationStepper flightStepper = new RK4SimulationStepper();
|
||||
private SimulationStepper landingStepper = new BasicLandingStepper();
|
||||
private SimulationStepper tumbleStepper = new BasicTumbleStepper();
|
||||
|
||||
private SimulationStepper currentStepper;
|
||||
|
||||
@ -191,6 +192,20 @@ public class BasicEventSimulationEngine implements SimulationEngine {
|
||||
(RocketComponent) status.getMotorConfiguration().getMotorMount(motorId), motorId));
|
||||
}
|
||||
}
|
||||
|
||||
// Check for Tumbling
|
||||
// FIXME - need to test things like no longer stable.
|
||||
|
||||
if (status.isApogeeReached() && !status.isTumbling() ) {
|
||||
int last_data_index = status.getFlightData().getLength() -1;
|
||||
double cp = status.getFlightData().get(FlightDataType.TYPE_CP_LOCATION).get(last_data_index);
|
||||
double cg = status.getFlightData().get(FlightDataType.TYPE_CG_LOCATION).get(last_data_index);
|
||||
if( cg > cp ) {
|
||||
addEvent( new FlightEvent(FlightEvent.Type.TUMBLE,status.getSimulationTime()));
|
||||
}
|
||||
status.setTumbling(true);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -479,6 +494,12 @@ public class BasicEventSimulationEngine implements SimulationEngine {
|
||||
|
||||
case ALTITUDE:
|
||||
break;
|
||||
|
||||
case TUMBLE:
|
||||
this.currentStepper = this.tumbleStepper;
|
||||
this.status = currentStepper.initialize(status);
|
||||
status.getFlightData().addEvent(event);
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
42
core/src/net/sf/openrocket/simulation/BasicTumbleStatus.java
Normal file
42
core/src/net/sf/openrocket/simulation/BasicTumbleStatus.java
Normal file
@ -0,0 +1,42 @@
|
||||
package net.sf.openrocket.simulation;
|
||||
|
||||
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;
|
||||
|
||||
public class BasicTumbleStatus extends SimulationStatus {
|
||||
|
||||
private double tumbleCd;
|
||||
|
||||
public BasicTumbleStatus(Configuration configuration,
|
||||
MotorInstanceConfiguration motorConfiguration,
|
||||
SimulationConditions simulationConditions) {
|
||||
super(configuration, motorConfiguration, simulationConditions);
|
||||
computeTumbleCd();
|
||||
}
|
||||
|
||||
public BasicTumbleStatus(SimulationStatus orig) {
|
||||
super(orig);
|
||||
if ( orig instanceof BasicTumbleStatus ) {
|
||||
this.tumbleCd = ((BasicTumbleStatus) orig).tumbleCd;
|
||||
}
|
||||
}
|
||||
|
||||
public double getTumbleCd( ) {
|
||||
return tumbleCd;
|
||||
}
|
||||
|
||||
|
||||
public void computeTumbleCd() {
|
||||
// FIXME - probably want to compute the overall CD more accurately. Perhaps average
|
||||
// CD over three AoA: 0, 90, 180. In any case, using barrowman to compute this Cd is
|
||||
// completely wrong.
|
||||
WarningSet warnings = new WarningSet();
|
||||
FlightConditions cond = new FlightConditions(this.getConfiguration());
|
||||
cond.setAOA(Math.PI);
|
||||
AerodynamicForces forces = this.getSimulationConditions().getAerodynamicCalculator().getAerodynamicForces(this.getConfiguration(), cond, warnings);
|
||||
tumbleCd = forces.getCD();
|
||||
}
|
||||
}
|
138
core/src/net/sf/openrocket/simulation/BasicTumbleStepper.java
Normal file
138
core/src/net/sf/openrocket/simulation/BasicTumbleStepper.java
Normal file
@ -0,0 +1,138 @@
|
||||
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 {
|
||||
double refArea = status.getConfiguration().getReferenceArea();
|
||||
|
||||
// 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 totalCD = ((BasicTumbleStatus)status).getTumbleCd();
|
||||
|
||||
// Compute drag force
|
||||
double dynP = (0.5 * atmosphere.getDensity() * airSpeed.length2());
|
||||
double dragForce = totalCD * dynP * refArea;
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
@ -72,7 +72,12 @@ public class FlightEvent implements Comparable<FlightEvent> {
|
||||
* A change in altitude has occurred. Data is a <code>Pair<Double,Double></code>
|
||||
* 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;
|
||||
|
||||
|
@ -69,6 +69,9 @@ public class SimulationStatus implements Monitorable {
|
||||
/** Set to true when apogee has been detected. */
|
||||
private boolean apogeeReached = false;
|
||||
|
||||
/** Set to true to indicate the rocket is tumbling. */
|
||||
private boolean tumbling = false;
|
||||
|
||||
/** Contains a list of deployed recovery devices. */
|
||||
private MonitorableSet<RecoveryDevice> deployedRecoveryDevices = new MonitorableSet<RecoveryDevice>();
|
||||
|
||||
@ -179,6 +182,7 @@ public class SimulationStatus implements Monitorable {
|
||||
this.liftoff = orig.liftoff;
|
||||
this.launchRodCleared = orig.launchRodCleared;
|
||||
this.apogeeReached = orig.apogeeReached;
|
||||
this.tumbling = orig.tumbling;
|
||||
this.motorBurntOut = orig.motorBurntOut;
|
||||
|
||||
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() {
|
||||
return deployedRecoveryDevices;
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user