[Refactor ] Refactored motor state in preparation for debugging the simulation itself

- Created MotorState as an enum describing discrete states a motor may be in
- moved ThrustCurveMotorState info back into MotorSimulation
  -- MotorSimulation will be used by the simulation code.
  -- tracks simulation-time info, such as event times, and current state ( <MotorState> )
- MotorConfiguration no longer have any knowledge about their simulation info
- moved functionality (BUT NOT STATE) into ThrustCurveMotor
  -- can query about thrust(t), mass(t), cgx(t)
This commit is contained in:
Daniel_M_Williams 2016-02-12 12:29:36 -05:00 committed by Daniel_M_Williams
parent 438f58c438
commit 28689825a4
27 changed files with 754 additions and 506 deletions

View File

@ -25,25 +25,37 @@ public class MassCalculator implements Monitorable {
public static enum MassCalcType { public static enum MassCalcType {
NO_MOTORS { NO_MOTORS {
@Override @Override
public Coordinate getCG(Motor motor) { public double getCGx(Motor motor) {
return Coordinate.NUL; return 0;
}
@Override
public double getMass(Motor motor) {
return 0;
} }
}, },
LAUNCH_MASS { LAUNCH_MASS {
@Override @Override
public Coordinate getCG(Motor motor) { public double getCGx(Motor motor) {
return motor.getLaunchCG(); return motor.getLaunchCGx();
}
@Override
public double getMass(Motor motor) {
return motor.getLaunchMass();
} }
}, },
BURNOUT_MASS { BURNOUT_MASS {
@Override @Override
public Coordinate getCG(Motor motor) { public double getCGx(Motor motor) {
return motor.getEmptyCG(); return motor.getBurnoutCGx();
}
@Override
public double getMass(Motor motor) {
return motor.getBurnoutMass();
} }
}; };
public abstract Coordinate getCG(Motor motor); public abstract double getMass(Motor motor);
public abstract double getCGx(Motor motor);
/** /**
* Compute the cg contribution of the motor relative to the rocket's coordinates * Compute the cg contribution of the motor relative to the rocket's coordinates
@ -52,16 +64,21 @@ public class MassCalculator implements Monitorable {
* @return * @return
*/ */
public Coordinate getCG(MotorConfiguration motorConfig) { public Coordinate getCG(MotorConfiguration motorConfig) {
Coordinate cg = getCG(motorConfig.getMotor()); Motor mtr = motorConfig.getMotor();
cg = cg.add(motorConfig.getPosition()); double mass = getMass(mtr);
Coordinate cg = motorConfig.getPosition().add( getCGx(mtr), 0, 0, mass);
RocketComponent motorMount = (RocketComponent) motorConfig.getMount(); RocketComponent motorMount = (RocketComponent) motorConfig.getMount();
Coordinate totalCG = new Coordinate(); Coordinate totalCM = new Coordinate();
for (Coordinate cord : motorMount.toAbsolute(cg) ) { for (Coordinate cord : motorMount.toAbsolute(cg) ) {
totalCG = totalCG.average(cord); totalCM = totalCM.average(cord);
} }
return totalCG; return totalCM;
}
public Coordinate getCM( Motor motor ){
return new Coordinate( getCGx(motor), 0, 0, getMass(motor));
} }
} }
@ -168,7 +185,7 @@ public class MassCalculator implements Monitorable {
double motorXPosition = mtrConfig.getX(); // location of motor from mount double motorXPosition = mtrConfig.getX(); // location of motor from mount
Coordinate localCM = type.getCG( mtr ); // CoM from beginning of motor Coordinate localCM = type.getCM( mtr ); // CoM from beginning of motor
localCM = localCM.setWeight( localCM.weight * instanceCount); localCM = localCM.setWeight( localCM.weight * instanceCount);
// a *bit* hacky :P // a *bit* hacky :P
Coordinate curMotorCM = localCM.setX( localCM.x + locations[0].x + motorXPosition ); Coordinate curMotorCM = localCM.setX( localCM.x + locations[0].x + motorXPosition );

View File

@ -1,9 +1,6 @@
package net.sf.openrocket.motor; package net.sf.openrocket.motor;
import net.sf.openrocket.simulation.MotorState; public interface Motor extends Cloneable {
import net.sf.openrocket.util.Coordinate;
public interface Motor {
/** /**
* Enum of rocket motor types. * Enum of rocket motor types.
@ -118,12 +115,19 @@ public interface Motor {
public String getDigest(); public String getDigest();
public MotorState getNewInstance(); public Motor clone();
public Coordinate getLaunchCG(); // this is probably a badly-designed way to expose the thrust, but it's not worth worrying about until
// there's a second (non-trivial) type of motor to support...
public double getThrustAtMotorTime( final double motorTimeDelta );
public Coordinate getEmptyCG(); public double getLaunchCGx();
public double getBurnoutCGx();
public double getLaunchMass();
public double getBurnoutMass();
/** /**
* Return an estimate of the burn time of this motor, or NaN if an estimate is unavailable. * Return an estimate of the burn time of this motor, or NaN if an estimate is unavailable.

View File

@ -4,7 +4,6 @@ import net.sf.openrocket.rocketcomponent.FlightConfigurableParameter;
import net.sf.openrocket.rocketcomponent.IgnitionEvent; import net.sf.openrocket.rocketcomponent.IgnitionEvent;
import net.sf.openrocket.rocketcomponent.MotorMount; import net.sf.openrocket.rocketcomponent.MotorMount;
import net.sf.openrocket.rocketcomponent.RocketComponent; import net.sf.openrocket.rocketcomponent.RocketComponent;
import net.sf.openrocket.simulation.MotorState;
import net.sf.openrocket.util.Coordinate; import net.sf.openrocket.util.Coordinate;
import net.sf.openrocket.util.Inertia; import net.sf.openrocket.util.Inertia;
@ -16,6 +15,7 @@ public class MotorConfiguration implements FlightConfigurableParameter<MotorConf
public static final String EMPTY_DESCRIPTION = "Empty Configuration"; public static final String EMPTY_DESCRIPTION = "Empty Configuration";
// set at config time
protected MotorMount mount = null; protected MotorMount mount = null;
protected Motor motor = null; protected Motor motor = null;
protected double ejectionDelay = 0.0; protected double ejectionDelay = 0.0;
@ -41,30 +41,10 @@ public class MotorConfiguration implements FlightConfigurableParameter<MotorConf
modID++; modID++;
} }
public MotorState getSimulationState() {
MotorState state = motor.getNewInstance();
state.setEjectionDelay( this.ejectionDelay );
if( ignitionOveride ) {
state.setIgnitionEvent( this.ignitionEvent );
state.setIgnitionDelay( this.ignitionDelay );
} else {
MotorConfiguration defInstance = mount.getDefaultMotorInstance();
state.setIgnitionEvent( defInstance.ignitionEvent );
state.setIgnitionDelay( defInstance.ignitionDelay );
}
state.setMount( mount );
state.setId( id );
return state;
}
public boolean hasIgnitionOverride() { public boolean hasIgnitionOverride() {
return ignitionOveride; return ignitionOveride;
} }
public boolean isActive() {
return motor != null;
}
public String getDescription(){ public String getDescription(){
if( motor == null ){ if( motor == null ){
return EMPTY_DESCRIPTION; return EMPTY_DESCRIPTION;
@ -164,7 +144,7 @@ public class MotorConfiguration implements FlightConfigurableParameter<MotorConf
public double getPropellantMass(){ public double getPropellantMass(){
if ( motor != null ) { if ( motor != null ) {
return (motor.getLaunchCG().weight - motor.getEmptyCG().weight); return (motor.getLaunchMass() - motor.getBurnoutMass());
} }
return 0.0; return 0.0;
} }

View File

@ -8,16 +8,13 @@ import java.util.Locale;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import net.sf.openrocket.util.ArrayUtils;
import net.sf.openrocket.util.BugException; import net.sf.openrocket.util.BugException;
import net.sf.openrocket.util.Coordinate; import net.sf.openrocket.util.Coordinate;
import net.sf.openrocket.util.Inertia;
import net.sf.openrocket.util.MathUtil; import net.sf.openrocket.util.MathUtil;
public class ThrustCurveMotor implements Motor, Comparable<ThrustCurveMotor>, Serializable { public class ThrustCurveMotor implements Motor, Comparable<ThrustCurveMotor>, Serializable {
/** // NECESSARY field, for this class -- this class is serialized in the motor database, and loaded directly.
*
*/
private static final long serialVersionUID = -1490333207132694479L; private static final long serialVersionUID = -1490333207132694479L;
@SuppressWarnings("unused") @SuppressWarnings("unused")
@ -45,13 +42,18 @@ public class ThrustCurveMotor implements Motor, Comparable<ThrustCurveMotor>, Se
private final double length; private final double length;
private final double[] time; private final double[] time;
private final double[] thrust; private final double[] thrust;
private final Coordinate[] cg; // private final double[] cgx; // cannot add without rebuilding the motor database ... automatically on every user's install.
// private final double[] mass; // cannot add without rebuilding the motor database ... on every user's install.
private final Coordinate[] cg; /// @deprecated, but required b/c the motor database is serialized java classes.
private double maxThrust; private double maxThrust;
private double burnTime; private double burnTime;
private double averageThrust; private double averageThrust;
private double totalImpulse; private double totalImpulse;
private final double unitRotationalInertia;
private final double unitLongitudinalInertia;
/** /**
* Deep copy constructor. * Deep copy constructor.
* Constructs a new ThrustCurveMotor from an existing ThrustCurveMotor. * Constructs a new ThrustCurveMotor from an existing ThrustCurveMotor.
@ -63,19 +65,22 @@ public class ThrustCurveMotor implements Motor, Comparable<ThrustCurveMotor>, Se
this.designation = m.designation; this.designation = m.designation;
this.description = m.description; this.description = m.description;
this.type = m.type; this.type = m.type;
this.delays = ArrayUtils.copyOf(m.delays, m.delays.length); this.delays = Arrays.copyOf(m.delays, m.delays.length);
this.diameter = m.diameter; this.diameter = m.diameter;
this.length = m.length; this.length = m.length;
this.time = ArrayUtils.copyOf(m.time, m.time.length); this.time = Arrays.copyOf(m.time, m.time.length);
this.thrust = ArrayUtils.copyOf(m.thrust, m.thrust.length); this.thrust = Arrays.copyOf(m.thrust, m.thrust.length);
this.cg = new Coordinate[m.cg.length]; this.cg = m.getCGPoints().clone();
for (int i = 0; i < cg.length; i++) { // this.cgx = Arrays.copyOf(m.cgx, m.cgx.length);
this.cg[i] = m.cg[i].clone(); // this.mass = Arrays.copyOf(m.mass, m.mass.length);
}
this.maxThrust = m.maxThrust; this.maxThrust = m.maxThrust;
this.burnTime = m.burnTime; this.burnTime = m.burnTime;
this.averageThrust = m.averageThrust; this.averageThrust = m.averageThrust;
this.totalImpulse = m.totalImpulse; this.totalImpulse = m.totalImpulse;
this.unitRotationalInertia = m.unitRotationalInertia;
this.unitLongitudinalInertia = m.unitLongitudinalInertia;
} }
/** /**
@ -162,8 +167,19 @@ public class ThrustCurveMotor implements Motor, Comparable<ThrustCurveMotor>, Se
this.time = time.clone(); this.time = time.clone();
this.thrust = thrust.clone(); this.thrust = thrust.clone();
this.cg = cg.clone(); this.cg = cg.clone();
// this.cgx = new double[ cg.length];
// this.mass = new double[ cg.length];
// for (int cgIndex = 0; cgIndex < cg.length; ++cgIndex){
// this.cgx[cgIndex] = cg[cgIndex].x;
// this.mass[cgIndex] = cg[cgIndex].weight;
// }
unitRotationalInertia = Inertia.filledCylinderRotational( this.diameter / 2);
unitLongitudinalInertia = Inertia.filledCylinderLongitudinal( this.diameter / 2, this.length);
computeStatistics(); computeStatistics();
// This constructor is not called upon serialized data constructor.
//System.err.println("loading motor: "+designation);
} }
@ -186,6 +202,65 @@ public class ThrustCurveMotor implements Motor, Comparable<ThrustCurveMotor>, Se
return time.clone(); return time.clone();
} }
/*
* find the index to data that corresponds to the given time:
*
* Pseudo Index is two parts:
* integer : simple index into the array
* fractional part: [0,1]: weighting to interpolate between the given index and the next index.
*
* @param is time after motor ignition, in seconds
* @return a pseudo index to this motor's data.
*/
public double getPseudoIndex( final double motorTime ){
final double SNAP_DISTANCE = 0.001;
if( time.length == 0 ){
return Double.NaN;
}
if( 0 > motorTime ){
return 0.0;
}
int lowerBoundIndex=0;
int upperBoundIndex=0;
while( ( upperBoundIndex < time.length ) && ( motorTime >= time[upperBoundIndex] )){
++upperBoundIndex;
}
lowerBoundIndex = upperBoundIndex-1;
if( upperBoundIndex == time.length ){
return time.length - 1;
}
if ( SNAP_DISTANCE > Math.abs( motorTime - time[lowerBoundIndex])){
return lowerBoundIndex;
}
double lowerBoundTime = time[lowerBoundIndex];
double upperBoundTime = time[upperBoundIndex];
double timeFraction = motorTime - lowerBoundTime;
double indexFraction = ( timeFraction ) / ( upperBoundTime - lowerBoundTime );
if( indexFraction < SNAP_DISTANCE ){
// round down to last index
return lowerBoundIndex;
}else if( indexFraction > (1-SNAP_DISTANCE)){
// round up to next index
return ++lowerBoundIndex;
}else{
// general case
return lowerBoundIndex + indexFraction;
}
}
@Override
public double getThrustAtMotorTime( final double searchTime ){
double pseudoIndex = getPseudoIndex( searchTime );
return getThrustAtIndex( pseudoIndex );
}
/** /**
* Returns the array of thrust points for this thrust curve. * Returns the array of thrust points for this thrust curve.
* @return an array of thrust samples * @return an array of thrust samples
@ -194,14 +269,26 @@ public class ThrustCurveMotor implements Motor, Comparable<ThrustCurveMotor>, Se
return thrust.clone(); return thrust.clone();
} }
/** // /**
* Returns the array of CG points for this thrust curve. // * Returns the array of CG points for this thrust curve.
* @return an array of CG samples // * @return an array of CG samples
*/ // */
public Coordinate[] getCGPoints() { // public double[] getCGxPoints() {
// return cgx;
// }
public Coordinate[] getCGPoints(){
return cg.clone(); return cg.clone();
} }
// /**
// * Returns the array of Mass values for this thrust curve.
// * @return an array of Masses
// */
// public double[] getMassPoints() {
// return mass;
// }
/** /**
* Return a list of standard delays defined for this motor. * Return a list of standard delays defined for this motor.
* @return a list of standard delays * @return a list of standard delays
@ -221,6 +308,25 @@ public class ThrustCurveMotor implements Motor, Comparable<ThrustCurveMotor>, Se
return type; return type;
} }
public double getUnitLongitudinalInertia() {
return this.unitLongitudinalInertia;
}
public double getUnitRotationalInertia() {
return this.unitRotationalInertia;
}
public double getIxxAtTime( final double searchTime) {
final double index = getPseudoIndex( searchTime);
//return this.unitLongitudinalInertia * this.getMassAtIndex( index);
return this.unitLongitudinalInertia * this.getCGAtIndex( index).weight;
}
public double getIyyAtTime( final double searchTime) {
final double index = getPseudoIndex( searchTime);
//return this.unitRotationalInertia * this.getMassAtIndex( index);
return this.unitRotationalInertia * this.getCGAtIndex( index).weight;
}
@Override @Override
public String getDesignation() { public String getDesignation() {
@ -248,29 +354,104 @@ public class ThrustCurveMotor implements Motor, Comparable<ThrustCurveMotor>, Se
return length; return length;
} }
@Override @Override
public ThrustCurveMotorState getNewInstance() { public ThrustCurveMotor clone() {
return new ThrustCurveMotorState(this); return new ThrustCurveMotor(this);
}
@Override
public Coordinate getLaunchCG() {
return cg[0];
} }
@Override @Override
public Coordinate getEmptyCG() { public double getLaunchCGx() {
return cg[cg.length - 1]; return cg[0].x;//cgx[0];
} }
// Coordinate getCG(int index) @Override
// double getThrust( int index) public double getBurnoutCGx() {
// double getTime( int index) return cg[cg.length - 1].x;// cgx[ cg.length - 1];
}
@Override
public double getLaunchMass() {
return cg[0].weight;//mass[0];
}
@Override
public double getBurnoutMass() {
return cg[cg.length-1].weight; //mass[mass.length - 1];
}
private static double interpolateValueAtIndex( final double[] values, final double pseudoIndex ){
final double SNAP_TOLERANCE = 0.0001;
final double upperFrac = pseudoIndex%1;
final double lowerFrac = 1-upperFrac;
final int lowerIndex = (int)pseudoIndex;
final int upperIndex= lowerIndex+1;
// if the pseudo
if( SNAP_TOLERANCE > (1-lowerFrac) ){
// index ~= int ... therefore:
return values[ (int) pseudoIndex ];
}else if( SNAP_TOLERANCE > upperFrac ){
return values[ (int)upperIndex ];
}
final double lowerValue = values[lowerIndex];
final double upperValue = values[upperIndex];
// return simple linear interpolation
return ( lowerValue*lowerFrac + upperValue*upperFrac );
}
public double getThrustAtIndex( final double pseudoIndex ){
return interpolateValueAtIndex( this.thrust, pseudoIndex );
}
public double getMotorTimeAtIndex( final double index ){
return interpolateValueAtIndex( this.time, index );
}
@Deprecated
public Coordinate getCGAtIndex( final double pseudoIndex ){
final double SNAP_TOLERANCE = 0.0001;
final double upperFrac = pseudoIndex%1;
final double lowerFrac = 1-upperFrac;
final int lowerIndex = (int)pseudoIndex;
final int upperIndex= lowerIndex+1;
// if the pseudo
if( SNAP_TOLERANCE > (1-lowerFrac) ){
// index ~= int ... therefore:
return cg[ (int) pseudoIndex ];
}else if( SNAP_TOLERANCE > upperFrac ){
return cg[ (int)upperIndex ];
}
final Coordinate lowerValue = cg[lowerIndex].multiply(lowerFrac);
final Coordinate upperValue = cg[upperIndex].multiply(upperFrac);
// return simple linear interpolation
return lowerValue.add( upperValue );
}
// public double getCGxAtIndex( final double index){
// //return interpolateValueAtIndex( this.cgx, index );
//
// }
//
// public double getMassAtIndex( final double index){
// //return interpolateValueAtIndex( this.mass, index );
//
// }
// int getCutoffIndex();
// double getCutoffTime() // double getCutoffTime()
// int getCutoffIndex(); // int getCutoffIndex();
// double interpolateThrust(...)
// Coordinate interpolateCG( ... ) // Coordinate interpolateCG( ... )
// //
@ -304,7 +485,7 @@ public class ThrustCurveMotor implements Motor, Comparable<ThrustCurveMotor>, Se
} }
public double getCutOffTime() { public double getCutOffTime() {
return this.time[this.time.length - 1]; return time[time.length - 1];
} }
/** /**

View File

@ -1,8 +1,6 @@
package net.sf.openrocket.motor; package net.sf.openrocket.motor;
import net.sf.openrocket.simulation.MotorState;
import net.sf.openrocket.util.BugException; import net.sf.openrocket.util.BugException;
import net.sf.openrocket.util.Coordinate;
public class ThrustCurveMotorPlaceholder implements Motor { public class ThrustCurveMotorPlaceholder implements Motor {
@ -75,20 +73,10 @@ public class ThrustCurveMotorPlaceholder implements Motor {
} }
@Override @Override
public MotorState getNewInstance() { public Motor clone() {
throw new BugException("Called getInstance on PlaceholderMotor"); throw new BugException("Called getInstance on PlaceholderMotor");
} }
@Override
public Coordinate getLaunchCG() {
return new Coordinate(length / 2, 0, 0, launchMass);
}
@Override
public Coordinate getEmptyCG() {
return new Coordinate(length / 2, 0, 0, emptyMass);
}
@Override @Override
public double getBurnTimeEstimate() { public double getBurnTimeEstimate() {
return Double.NaN; return Double.NaN;
@ -184,4 +172,30 @@ public class ThrustCurveMotorPlaceholder implements Motor {
+ ", designation=" + designation + "]"; + ", designation=" + designation + "]";
} }
@Override
public double getLaunchCGx() {
return length / 2;
}
@Override
public double getBurnoutCGx() {
return length / 2;
}
@Override
public double getLaunchMass() {
return launchMass;
}
@Override
public double getBurnoutMass() {
return emptyMass;
}
@Override
public double getThrustAtMotorTime(double pseudoIndex) {
return 0;
}
} }

View File

@ -1,257 +0,0 @@
package net.sf.openrocket.motor;
import net.sf.openrocket.models.atmosphere.AtmosphericConditions;
import net.sf.openrocket.rocketcomponent.IgnitionEvent;
import net.sf.openrocket.rocketcomponent.MotorMount;
import net.sf.openrocket.simulation.MotorState;
import net.sf.openrocket.util.BugException;
import net.sf.openrocket.util.Coordinate;
import net.sf.openrocket.util.Inertia;
import net.sf.openrocket.util.MathUtil;
public class ThrustCurveMotorState implements MotorState {
// private static final Logger log = LoggerFactory.getLogger(ThrustCurveMotorInstance.class);
private int timeIndex = -1;
protected MotorMount mount = null;
protected MotorInstanceId id = null;
private double ignitionTime = -1;
private double ignitionDelay;
private IgnitionEvent ignitionEvent;
private double ejectionDelay;
protected ThrustCurveMotor motor = null;
// Previous time step value
private double prevTime = Double.NaN;
// Average thrust during previous step
private double stepThrust = Double.NaN;
// Instantaneous thrust at current time point
private double instThrust = Double.NaN;
// Average CG during previous step
private Coordinate stepCG = Coordinate.ZERO;
// Instantaneous CG at current time point
private Coordinate instCG = Coordinate.ZERO;
private final double unitRotationalInertia;
private final double unitLongitudinalInertia;
// // please use the Motor Constructor below, instead.
// @SuppressWarnings("unused")
// private ThrustCurveMotorInstance() {
// unitRotationalInertia = Double.NaN;
// unitLongitudinalInertia = Double.NaN;
// }
public ThrustCurveMotorState(final ThrustCurveMotor source) {
//log.debug( Creating motor instance of " + ThrustCurveMotor.this);
this.motor = source;
this.reset();
unitRotationalInertia = Inertia.filledCylinderRotational(source.getDiameter() / 2);
unitLongitudinalInertia = Inertia.filledCylinderLongitudinal(source.getDiameter() / 2, source.getLength());
}
@Override
public ThrustCurveMotorState clone() {
try {
return (ThrustCurveMotorState) super.clone();
} catch (CloneNotSupportedException e) {
throw new BugException("CloneNotSupportedException", e);
}
}
@Override
public double getIgnitionTime() {
return ignitionTime;
}
@Override
public void setIgnitionTime(double ignitionTime) {
this.ignitionTime = ignitionTime;
}
@Override
public MotorMount getMount() {
return mount;
}
@Override
public void setMount(MotorMount mount) {
this.mount = mount;
}
@Override
public IgnitionEvent getIgnitionEvent() {
return ignitionEvent;
}
@Override
public void setIgnitionEvent(IgnitionEvent event) {
this.ignitionEvent = event;
}
@Override
public double getIgnitionDelay() {
return ignitionDelay;
}
@Override
public void setIgnitionDelay(double delay) {
this.ignitionDelay = delay;
}
@Override
public double getEjectionDelay() {
return ejectionDelay;
}
@Override
public void setEjectionDelay(double delay) {
this.ejectionDelay = delay;
}
@Override
public void setId(MotorInstanceId id) {
this.id = id;
}
@Override
public MotorInstanceId getID() {
return id;
}
public double getTime() {
return prevTime;
}
public Coordinate getCG() {
return stepCG;
}
public Coordinate getCM() {
return stepCG;
}
public double getPropellantMass(){
return (motor.getLaunchCG().weight - motor.getEmptyCG().weight);
}
public double getLongitudinalInertia() {
return unitLongitudinalInertia * stepCG.weight;
}
public double getRotationalInertia() {
return unitRotationalInertia * stepCG.weight;
}
@Override
public double getThrust() {
return stepThrust;
}
@Override
public boolean isActive() {
return prevTime < motor.getCutOffTime();
}
public Motor getMotor(){
return this.motor;
}
public boolean isEmpty(){
return false;
}
@Override
public void step(double nextTime, double acceleration, AtmosphericConditions cond) {
if (MathUtil.equals(prevTime, nextTime)) {
return;
}
double[] time = motor.getTimePoints();
double[] thrust = motor.getThrustPoints();
Coordinate[] cg = motor.getCGPoints();
if (timeIndex >= (motor.getDataSize() - 1)) {
// Thrust has ended
prevTime = nextTime;
stepThrust = 0;
instThrust = 0;
stepCG = motor.getEmptyCG();
return;
}
// Compute average & instantaneous thrust
if (nextTime < time[timeIndex + 1]) {
// Time step between time points
double nextF = MathUtil.map(nextTime, time[timeIndex], time[timeIndex + 1],
thrust[timeIndex], thrust[timeIndex + 1]);
stepThrust = (instThrust + nextF) / 2;
instThrust = nextF;
} else {
// Portion of previous step
stepThrust = (instThrust + thrust[timeIndex + 1]) / 2 * (time[timeIndex + 1] - prevTime);
// Whole steps
timeIndex++;
while ((timeIndex < time.length - 1) && (nextTime >= time[timeIndex + 1])) {
stepThrust += (thrust[timeIndex] + thrust[timeIndex + 1]) / 2 *
(time[timeIndex + 1] - time[timeIndex]);
timeIndex++;
}
// End step
if (timeIndex < time.length - 1) {
instThrust = MathUtil.map(nextTime, time[timeIndex], time[timeIndex + 1],
thrust[timeIndex], thrust[timeIndex + 1]);
stepThrust += (thrust[timeIndex] + instThrust) / 2 *
(nextTime - time[timeIndex]);
} else {
// Thrust ended during this step
instThrust = 0;
}
stepThrust /= (nextTime - prevTime);
}
// Compute average and instantaneous CG (simple average between points)
Coordinate nextCG;
if (timeIndex < time.length - 1) {
nextCG = MathUtil.map(nextTime, time[timeIndex], time[timeIndex + 1],
cg[timeIndex], cg[timeIndex + 1]);
} else {
nextCG = cg[cg.length - 1];
}
stepCG = instCG.add(nextCG).multiply(0.5);
instCG = nextCG;
// Update time
prevTime = nextTime;
}
public void reset(){
timeIndex = 0;
prevTime = 0;
instThrust = 0;
stepThrust = 0;
instCG = motor.getLaunchCG();
stepCG = instCG;
}
@Override
public String toString(){
return this.motor.getDesignation();
}
}

View File

@ -528,12 +528,11 @@ public class FlightConfiguration implements FlightConfigurableParameter<FlightCo
StringBuilder buf = new StringBuilder(); StringBuilder buf = new StringBuilder();
buf.append(String.format("\nDumping %2d Motors for configuration %s: (#: %s)\n", this.motors.size(), this, this.instanceNumber)); buf.append(String.format("\nDumping %2d Motors for configuration %s: (#: %s)\n", this.motors.size(), this, this.instanceNumber));
final String fmt = " ..[%-8s] <%6s> %-12s %-20s\n"; final String fmt = " ..[%-8s] <%6s> %-12s %-20s\n";
buf.append(String.format(fmt, "Motor Id", "?active", "Mtr Desig","Mount")); buf.append(String.format(fmt, "Motor Id", "Mtr Desig","Mount"));
for( MotorConfiguration curConfig : this.motors.values() ){ for( MotorConfiguration curConfig : this.motors.values() ){
MotorMount mount = curConfig.getMount(); MotorMount mount = curConfig.getMount();
String motorId = curConfig.getID().toShortKey(); String motorId = curConfig.getID().toShortKey();
String activeDescr = (curConfig.isActive()? "active": "inactv");
String motorDesig; String motorDesig;
if( curConfig.isEmpty() ){ if( curConfig.isEmpty() ){
motorDesig = "(empty)"; motorDesig = "(empty)";
@ -542,7 +541,7 @@ public class FlightConfiguration implements FlightConfigurableParameter<FlightCo
} }
String mountName = ((RocketComponent)mount).getName(); String mountName = ((RocketComponent)mount).getName();
buf.append(String.format( fmt, motorId, activeDescr, motorDesig, mountName)); buf.append(String.format( fmt, motorId, motorDesig, mountName));
} }
buf.append("\n"); buf.append("\n");
return buf.toString(); return buf.toString();

View File

@ -170,19 +170,13 @@ public abstract class AbstractSimulationStepper implements SimulationStepper {
thrust = 0; thrust = 0;
final double currentTime = status.getSimulationTime() + timestep; final double currentTime = status.getSimulationTime() + timestep;
Collection<MotorState> activeMotorList = status.getActiveMotors(); Collection<MotorSimulation> activeMotorList = status.getMotors();
for (MotorState currentMotorInstance : activeMotorList ) { for (MotorSimulation currentMotorState : activeMotorList ) {
if ( !stepMotors ) { if ( !stepMotors ) {
currentMotorInstance = currentMotorInstance.clone(); currentMotorState = currentMotorState.clone();
}
// old: transplanted from MotorInstanceConfiguration
double instanceTime = currentTime - currentMotorInstance.getIgnitionTime();
if (instanceTime >= 0) {
currentMotorInstance.step(instanceTime, acceleration, atmosphericConditions);
} }
// old: from here thrust += currentMotorState.getThrust( currentTime, atmosphericConditions );
thrust += currentMotorInstance.getThrust();
} }
// Post-listeners // Post-listeners

View File

@ -194,11 +194,19 @@ public class BasicEventSimulationEngine implements SimulationEngine {
// Check for burnt out motors // Check for burnt out motors
for( MotorState motor : currentStatus.getAllMotors()){ for( MotorSimulation state : currentStatus.getAllMotors()){
MotorInstanceId motorId = motor.getID();
if (!motor.isActive() && currentStatus.addBurntOutMotor(motorId)) { state.isFinishedThrusting( );
MotorInstanceId motorId = state.getID();
if ( state.isFinishedThrusting()){
// TODO : implement me!
//currentStatus.moveBurntOutMotor( motorId);
currentStatus.addBurntOutMotor(motorId);
addEvent(new FlightEvent(FlightEvent.Type.BURNOUT, currentStatus.getSimulationTime(), addEvent(new FlightEvent(FlightEvent.Type.BURNOUT, currentStatus.getSimulationTime(),
(RocketComponent) motor.getMount(), motorId)); (RocketComponent) state.getMount(), motorId));
} }
} }
@ -275,7 +283,7 @@ public class BasicEventSimulationEngine implements SimulationEngine {
if (event.getType() == FlightEvent.Type.IGNITION) { if (event.getType() == FlightEvent.Type.IGNITION) {
MotorMount mount = (MotorMount) event.getSource(); MotorMount mount = (MotorMount) event.getSource();
MotorInstanceId motorId = (MotorInstanceId) event.getData(); MotorInstanceId motorId = (MotorInstanceId) event.getData();
MotorState instance = currentStatus.getMotor(motorId); MotorSimulation instance = currentStatus.getMotor(motorId);
if (!SimulationListenerHelper.fireMotorIgnition(currentStatus, motorId, mount, instance)) { if (!SimulationListenerHelper.fireMotorIgnition(currentStatus, motorId, mount, instance)) {
continue; continue;
} }
@ -288,7 +296,7 @@ public class BasicEventSimulationEngine implements SimulationEngine {
} }
// Check for motor ignition events, add ignition events to queue // Check for motor ignition events, add ignition events to queue
for (MotorState inst : currentStatus.getActiveMotors() ){ for (MotorSimulation inst : currentStatus.getAllMotors() ){
IgnitionEvent ignitionEvent = inst.getIgnitionEvent(); IgnitionEvent ignitionEvent = inst.getIgnitionEvent();
MotorMount mount = inst.getMount(); MotorMount mount = inst.getMount();
RocketComponent component = (RocketComponent) mount; RocketComponent component = (RocketComponent) mount;
@ -296,7 +304,7 @@ public class BasicEventSimulationEngine implements SimulationEngine {
if (ignitionEvent.isActivationEvent(event, component)) { if (ignitionEvent.isActivationEvent(event, component)) {
double ignitionDelay = inst.getIgnitionDelay(); double ignitionDelay = inst.getIgnitionDelay();
// TODO: this event seems to get enque'd multiple times -- more than necessary... // TODO: this event seems to get enque'd multiple times -- more than necessary...
//System.err.println("Queing ignition of mtr:"+inst.getMotor().getDesignation()+" @"+currentStatus.getSimulationTime()); //System.err.println("Queueing ignition of mtr:"+inst.getMotor().getDesignation()+" @"+currentStatus.getSimulationTime());
addEvent(new FlightEvent(FlightEvent.Type.IGNITION, addEvent(new FlightEvent(FlightEvent.Type.IGNITION,
currentStatus.getSimulationTime() + ignitionDelay, currentStatus.getSimulationTime() + ignitionDelay,
component, inst.getID() )); component, inst.getID() ));
@ -341,8 +349,8 @@ public class BasicEventSimulationEngine implements SimulationEngine {
case IGNITION: { case IGNITION: {
// Ignite the motor // Ignite the motor
MotorInstanceId motorId = (MotorInstanceId) event.getData(); MotorInstanceId motorId = (MotorInstanceId) event.getData();
MotorState inst = currentStatus.getMotor( motorId); MotorSimulation inst = currentStatus.getMotor( motorId);
inst.setIgnitionTime(event.getTime()); inst.ignite( event.getTime());
//System.err.println("Igniting motor: "+inst.getMotor().getDesignation()+" @"+currentStatus.getSimulationTime()); //System.err.println("Igniting motor: "+inst.getMotor().getDesignation()+" @"+currentStatus.getSimulationTime());
currentStatus.setMotorIgnited(true); currentStatus.setMotorIgnited(true);
@ -370,10 +378,11 @@ public class BasicEventSimulationEngine implements SimulationEngine {
if (!currentStatus.isLiftoff()) { if (!currentStatus.isLiftoff()) {
throw new SimulationLaunchException(trans.get("BasicEventSimulationEngine.error.earlyMotorBurnout")); throw new SimulationLaunchException(trans.get("BasicEventSimulationEngine.error.earlyMotorBurnout"));
} }
// Add ejection charge event // Add ejection charge event
MotorInstanceId motorId = (MotorInstanceId) event.getData(); MotorInstanceId motorId = (MotorInstanceId) event.getData();
MotorState motor = currentStatus.getMotor( motorId); MotorSimulation state = currentStatus.getMotor( motorId);
double delay = motor.getEjectionDelay(); double delay = state.getEjectionDelay();
if (delay != Motor.PLUGGED) { if (delay != Motor.PLUGGED) {
addEvent(new FlightEvent(FlightEvent.Type.EJECTION_CHARGE, currentStatus.getSimulationTime() + delay, addEvent(new FlightEvent(FlightEvent.Type.EJECTION_CHARGE, currentStatus.getSimulationTime() + delay,
event.getSource(), event.getData())); event.getSource(), event.getData()));

View File

@ -0,0 +1,160 @@
package net.sf.openrocket.simulation;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import net.sf.openrocket.models.atmosphere.AtmosphericConditions;
import net.sf.openrocket.motor.Motor;
import net.sf.openrocket.motor.MotorConfiguration;
import net.sf.openrocket.motor.MotorInstanceId;
import net.sf.openrocket.rocketcomponent.IgnitionEvent;
import net.sf.openrocket.rocketcomponent.MotorMount;
import net.sf.openrocket.util.BugException;
public class MotorSimulation {
private static final Logger log = LoggerFactory.getLogger(MotorSimulation.class);
// for reference: set at initialization ONLY.
final protected Motor motor;
final protected MotorConfiguration config;
final protected double thrustDuration;
// for state:
protected double ignitionTime = Double.NaN;
protected double cutoffTime = Double.NaN;
protected double ejectionTime = Double.NaN;
protected MotorState currentState = MotorState.PREFLIGHT;
public MotorSimulation(final MotorConfiguration _config) {
log.debug(" Creating motor instance of " + _config.getDescription());
this.config = _config;
this.motor = _config.getMotor();
thrustDuration = this.motor.getBurnTimeEstimate();
this.resetToPreflight();
}
@Override
public MotorSimulation clone() {
try {
return (MotorSimulation) super.clone();
} catch (CloneNotSupportedException e) {
throw new BugException("CloneNotSupportedException", e);
}
}
public void arm( final double _armTime ){
if( MotorState.PREFLIGHT == currentState ){
log.info( "igniting motor: "+this.toString()+" at t="+_armTime);
//this.ignitionTime = _ignitionTime;
this.currentState = this.currentState.getNext();
}else{
throw new IllegalStateException("Attempted to arm a motor with status="+this.currentState.getName());
}
}
public boolean isFinishedThrusting(){
return currentState.isAfter( MotorState.THRUSTING );
}
public double getIgnitionTime() {
return ignitionTime;
}
public IgnitionEvent getIgnitionEvent() {
return config.getIgnitionEvent();
}
public void ignite( final double _ignitionTime ){
if( MotorState.ARMED == currentState ){
log.info( "igniting motor: "+this.toString()+" at t="+_ignitionTime);
this.ignitionTime = _ignitionTime;
this.currentState = this.currentState.getNext();
}else{
throw new IllegalStateException("Attempted to ignite a motor state with status="+this.currentState.getName());
}
}
public void burnOut( final double _burnOutTime ){
if( MotorState.THRUSTING == currentState ){
log.info( "igniting motor: "+this.toString()+" at t="+_burnOutTime);
this.ignitionTime = _burnOutTime;
this.currentState = this.currentState.getNext();
}else{
throw new IllegalStateException("Attempted to stop thrust (burn-out) a motor state with status="+this.currentState.getName());
}
}
public void fireEjectionCharge( final double _ejectionTime ){
if( MotorState.DELAYING == currentState ){
log.info( "igniting motor: "+this.toString()+" at t="+_ejectionTime);
this.ejectionTime = _ejectionTime;
this.currentState = this.currentState.getNext();
}else{
throw new IllegalStateException("Attempted to fire an ejection charge in motor state: "+this.currentState.getName());
}
}
/*
* Alias for "burnOut(double)"
*/
public void cutOff( final double _cutoffTime ){
burnOut( _cutoffTime );
}
public double getIgnitionDelay() {
return config.getEjectionDelay();
}
public double getEjectionDelay() {
return config.getEjectionDelay();
}
public MotorInstanceId getID() {
return config.getID();
}
public double getPropellantMass(){
return (motor.getLaunchMass() - motor.getBurnoutMass());
}
// public boolean isActive( ) {
// return this.currentState.isActive();
// }
public MotorMount getMount(){
return config.getMount();
}
public Motor getMotor(){
return this.motor;
}
double getCutOffTime(){
return this.cutoffTime;
}
public double getThrust( final double simTime, final AtmosphericConditions cond){
if( this.currentState.isThrusting() ){
return motor.getThrustAtMotorTime( simTime - this.getIgnitionTime());
}else{
return 0.0;
}
}
public void resetToPreflight(){
ignitionTime = Double.NaN;
cutoffTime = Double.NaN;
ejectionTime = Double.NaN;
currentState = MotorState.PREFLIGHT;
}
@Override
public String toString(){
return this.motor.getDesignation();
}
}

View File

@ -1,34 +1,104 @@
package net.sf.openrocket.simulation; package net.sf.openrocket.simulation;
import net.sf.openrocket.models.atmosphere.AtmosphericConditions;
import net.sf.openrocket.motor.MotorInstanceId;
import net.sf.openrocket.rocketcomponent.IgnitionEvent;
import net.sf.openrocket.rocketcomponent.MotorMount;
public interface MotorState extends Cloneable { public enum MotorState {
FINISHED("Finished with sequence.", "Finished Producing thrust.", null)
,DELAYING("Delaying", " After Burnout, but before ejection", FINISHED){
@Override
public boolean needsSimulation(){ return true;}
}
,THRUSTING("Thrusting", "Currently Producing thrust", DELAYING){
@Override
public boolean isThrusting(){ return true; }
@Override
public boolean needsSimulation(){ return true;}
}
,ARMED("Armed", "Armed, but not yet lit.", FINISHED)
,PREFLIGHT("Pre-Launch", "Safed and inactive, prior to launch.", FINISHED)
;
public void step(double nextTime, double acceleration, AtmosphericConditions cond); private final static int SEQUENCE_NUMBER_END = 10; // arbitrary number
public double getThrust();
public boolean isActive();
public double getIgnitionTime(); private final String name;
public void setIgnitionTime( double ignitionTime ); private final String description;
private final int sequenceNumber;
private final MotorState nextState;
public void setMount(MotorMount mount); MotorState( final String name, final String description, final MotorState _nextState) {
public MotorMount getMount(); this.name = name;
this.description = description;
if( null == _nextState ){
this.sequenceNumber = SEQUENCE_NUMBER_END;
this.nextState = null;
}else{
this.sequenceNumber = -1 + _nextState.getSequenceNumber() ;
this.nextState = _nextState;
}
}
public void setId(MotorInstanceId id); /**
public MotorInstanceId getID(); * Return a short name of this motor type.
* @return a short name of the motor type.
*/
public String getName() {
return this.name;
}
public IgnitionEvent getIgnitionEvent();
public void setIgnitionEvent( IgnitionEvent event );
public double getIgnitionDelay(); /*
public void setIgnitionDelay( double delay ); *
* @Return a MotorState enum telling what state should follow this one.
*/
public MotorState getNext( ){
return this.nextState;
}
public double getEjectionDelay(); /**
public void setEjectionDelay( double delay); * Return a long description of this motor type.
* @return a description of the motor type.
*/
public String getDescription() {
return this.description;
}
public MotorState clone(); /**
* Sequence numbers have no intrinsic meaning, but their sequence (and relative value)
* indicate which states occur before other states.
* @see isAfter(MotorState)
* @see isBefore(MotorState)
* @return integer indicating this state's place in the allowable sequence
*/
public int getSequenceNumber(){
return this.sequenceNumber;
}
public boolean isAfter( final MotorState other ){
return this.getSequenceNumber() > other.getSequenceNumber();
}
public boolean isBefore( final MotorState other ){
return this.getSequenceNumber() < other.getSequenceNumber();
}
/*
* If this motor is in a state which produces thrust
*/
public boolean isThrusting(){
return false;
}
/**
* This flag determines whether the motor has its state updated, and updates of cg and thrust updated.
* A motor instance will always receive events -- which may affect the simulation yes/no state
*
* @return should this motor be simulated
*/
public boolean needsSimulation(){
return false;
}
@Override
public String toString() {
return name;
}
} }

View File

@ -49,9 +49,9 @@ public class SimulationStatus implements Monitorable {
private double effectiveLaunchRodLength; private double effectiveLaunchRodLength;
// Set of burnt out motors // Set of burnt out motors
Set<MotorInstanceId> motorBurntOut = new HashSet<MotorInstanceId>(); Set<MotorInstanceId> spentMotors = new HashSet<MotorInstanceId>();
List<MotorState> motorState = new ArrayList<MotorState>(); List<MotorSimulation> motorStateList = new ArrayList<MotorSimulation>();
/** Nanosecond time when the simulation was started. */ /** Nanosecond time when the simulation was started. */
private long simulationStartWallTime = Long.MIN_VALUE; private long simulationStartWallTime = Long.MIN_VALUE;
@ -148,11 +148,10 @@ public class SimulationStatus implements Monitorable {
this.launchRodCleared = false; this.launchRodCleared = false;
this.apogeeReached = false; this.apogeeReached = false;
for( MotorConfiguration motorInstance : this.configuration.getActiveMotors() ) { for( MotorConfiguration motorConfig : this.configuration.getActiveMotors() ) {
this.motorState.add( motorInstance.getSimulationState() ); this.motorStateList.add( new MotorSimulation( motorConfig) );
} }
this.warnings = new WarningSet(); this.warnings = new WarningSet();
} }
/** /**
@ -185,14 +184,14 @@ public class SimulationStatus implements Monitorable {
this.launchRodCleared = orig.launchRodCleared; this.launchRodCleared = orig.launchRodCleared;
this.apogeeReached = orig.apogeeReached; this.apogeeReached = orig.apogeeReached;
this.tumbling = orig.tumbling; this.tumbling = orig.tumbling;
this.motorBurntOut = orig.motorBurntOut; this.spentMotors = orig.spentMotors;
this.deployedRecoveryDevices.clear(); this.deployedRecoveryDevices.clear();
this.deployedRecoveryDevices.addAll(orig.deployedRecoveryDevices); this.deployedRecoveryDevices.addAll(orig.deployedRecoveryDevices);
// FIXME - is this right? // FIXME - is this right?
this.motorState.clear(); this.motorStateList.clear();
this.motorState.addAll(orig.motorState); this.motorStateList.addAll(orig.motorStateList);
this.eventQueue.clear(); this.eventQueue.clear();
this.eventQueue.addAll(orig.eventQueue); this.eventQueue.addAll(orig.eventQueue);
@ -225,20 +224,24 @@ public class SimulationStatus implements Monitorable {
this.configuration = configuration; this.configuration = configuration;
} }
public Collection<MotorState> getAllMotors() { public Collection<MotorSimulation> getMotors() {
return motorState; return motorStateList;
} }
public Collection<MotorState> getActiveMotors() { public Collection<MotorSimulation> getAllMotors() {
List<MotorState> activeList = new ArrayList<MotorState>(); return motorStateList;
for( MotorState inst : this.motorState ){
if( inst.isActive() ){
activeList.add( inst );
}
} }
return activeList; // public Collection<MotorInstance> getActiveMotors() {
} // List<MotorInstance> activeList = new ArrayList<MotorInstance>();
// for( MotorInstance inst : this.motorStateList ){
// if( inst.isActive() ){
// activeList.add( inst );
// }
// }
//
// return activeList;
// }
public FlightConfiguration getConfiguration() { public FlightConfiguration getConfiguration() {
return configuration; return configuration;
@ -260,8 +263,8 @@ public class SimulationStatus implements Monitorable {
return flightData; return flightData;
} }
public MotorState getMotor( final MotorInstanceId motorId ){ public MotorSimulation getMotor( final MotorInstanceId motorId ){
for( MotorState state : motorState ) { for( MotorSimulation state : motorStateList ) {
if ( motorId.equals(state.getID() )) { if ( motorId.equals(state.getID() )) {
return state; return state;
} }
@ -310,8 +313,15 @@ public class SimulationStatus implements Monitorable {
} }
public boolean moveBurntOutMotor( final MotorInstanceId motor) {
// get motor from normal list
// remove motor from 'normal' list
// add to spent list
return false;
}
public boolean addBurntOutMotor(MotorInstanceId motor) { public boolean addBurntOutMotor(MotorInstanceId motor) {
return motorBurntOut.add(motor); return spentMotors.add(motor);
} }

View File

@ -18,7 +18,7 @@ import net.sf.openrocket.rocketcomponent.MotorMount;
import net.sf.openrocket.rocketcomponent.RecoveryDevice; import net.sf.openrocket.rocketcomponent.RecoveryDevice;
import net.sf.openrocket.simulation.AccelerationData; import net.sf.openrocket.simulation.AccelerationData;
import net.sf.openrocket.simulation.FlightEvent; import net.sf.openrocket.simulation.FlightEvent;
import net.sf.openrocket.simulation.MotorState; import net.sf.openrocket.simulation.MotorSimulation;
import net.sf.openrocket.simulation.SimulationStatus; import net.sf.openrocket.simulation.SimulationStatus;
import net.sf.openrocket.simulation.exception.SimulationException; import net.sf.openrocket.simulation.exception.SimulationException;
import net.sf.openrocket.simulation.exception.SimulationListenerException; import net.sf.openrocket.simulation.exception.SimulationListenerException;
@ -105,7 +105,7 @@ public class ScriptingSimulationListener implements SimulationListener, Simulati
} }
@Override @Override
public boolean motorIgnition(SimulationStatus status, MotorInstanceId motorId, MotorMount mount, MotorState instance) throws SimulationException { public boolean motorIgnition(SimulationStatus status, MotorInstanceId motorId, MotorMount mount, MotorSimulation instance) throws SimulationException {
return invoke(Boolean.class, true, "motorIgnition", status, motorId, mount, instance); return invoke(Boolean.class, true, "motorIgnition", status, motorId, mount, instance);
} }

View File

@ -9,7 +9,7 @@ import net.sf.openrocket.rocketcomponent.MotorMount;
import net.sf.openrocket.rocketcomponent.RecoveryDevice; import net.sf.openrocket.rocketcomponent.RecoveryDevice;
import net.sf.openrocket.simulation.AccelerationData; import net.sf.openrocket.simulation.AccelerationData;
import net.sf.openrocket.simulation.FlightEvent; import net.sf.openrocket.simulation.FlightEvent;
import net.sf.openrocket.simulation.MotorState; import net.sf.openrocket.simulation.MotorSimulation;
import net.sf.openrocket.simulation.SimulationStatus; import net.sf.openrocket.simulation.SimulationStatus;
import net.sf.openrocket.simulation.exception.SimulationException; import net.sf.openrocket.simulation.exception.SimulationException;
import net.sf.openrocket.util.BugException; import net.sf.openrocket.util.BugException;
@ -72,7 +72,7 @@ public class AbstractSimulationListener implements SimulationListener, Simulatio
} }
@Override @Override
public boolean motorIgnition(SimulationStatus status, MotorInstanceId motorId, MotorMount mount, MotorState instance) throws SimulationException { public boolean motorIgnition(SimulationStatus status, MotorInstanceId motorId, MotorMount mount, MotorSimulation instance) throws SimulationException {
return true; return true;
} }

View File

@ -4,7 +4,7 @@ import net.sf.openrocket.motor.MotorInstanceId;
import net.sf.openrocket.rocketcomponent.MotorMount; import net.sf.openrocket.rocketcomponent.MotorMount;
import net.sf.openrocket.rocketcomponent.RecoveryDevice; import net.sf.openrocket.rocketcomponent.RecoveryDevice;
import net.sf.openrocket.simulation.FlightEvent; import net.sf.openrocket.simulation.FlightEvent;
import net.sf.openrocket.simulation.MotorState; import net.sf.openrocket.simulation.MotorSimulation;
import net.sf.openrocket.simulation.SimulationStatus; import net.sf.openrocket.simulation.SimulationStatus;
import net.sf.openrocket.simulation.exception.SimulationException; import net.sf.openrocket.simulation.exception.SimulationException;
@ -44,7 +44,7 @@ public interface SimulationEventListener {
* @return <code>true</code> to ignite the motor, <code>false</code> to abort ignition * @return <code>true</code> to ignite the motor, <code>false</code> to abort ignition
*/ */
public boolean motorIgnition(SimulationStatus status, MotorInstanceId motorId, MotorMount mount, public boolean motorIgnition(SimulationStatus status, MotorInstanceId motorId, MotorMount mount,
MotorState instance) throws SimulationException; MotorSimulation instance) throws SimulationException;
/** /**

View File

@ -14,7 +14,7 @@ import net.sf.openrocket.rocketcomponent.MotorMount;
import net.sf.openrocket.rocketcomponent.RecoveryDevice; import net.sf.openrocket.rocketcomponent.RecoveryDevice;
import net.sf.openrocket.simulation.AccelerationData; import net.sf.openrocket.simulation.AccelerationData;
import net.sf.openrocket.simulation.FlightEvent; import net.sf.openrocket.simulation.FlightEvent;
import net.sf.openrocket.simulation.MotorState; import net.sf.openrocket.simulation.MotorSimulation;
import net.sf.openrocket.simulation.SimulationStatus; import net.sf.openrocket.simulation.SimulationStatus;
import net.sf.openrocket.simulation.exception.SimulationException; import net.sf.openrocket.simulation.exception.SimulationException;
import net.sf.openrocket.util.Coordinate; import net.sf.openrocket.util.Coordinate;
@ -168,7 +168,7 @@ public class SimulationListenerHelper {
* @return <code>true</code> to handle the event normally, <code>false</code> to skip event. * @return <code>true</code> to handle the event normally, <code>false</code> to skip event.
*/ */
public static boolean fireMotorIgnition(SimulationStatus status, MotorInstanceId motorId, MotorMount mount, public static boolean fireMotorIgnition(SimulationStatus status, MotorInstanceId motorId, MotorMount mount,
MotorState instance) throws SimulationException { MotorSimulation instance) throws SimulationException {
boolean b; boolean b;
int modID = status.getModID(); // Contains also motor instance int modID = status.getModID(); // Contains also motor instance

View File

@ -70,7 +70,7 @@ public class DampingMoment extends AbstractSimulationListener {
FlightConfiguration config = status.getConfiguration(); FlightConfiguration config = status.getConfiguration();
for (MotorConfiguration inst : config.getActiveMotors()) { for (MotorConfiguration inst : config.getActiveMotors()) {
double x_position= inst.getX(); double x_position= inst.getX();
double x = x_position + inst.getMotor().getLaunchCG().x; double x = x_position + inst.getMotor().getLaunchCGx();
if (x > nozzleDistance) { if (x > nozzleDistance) {
nozzleDistance = x; nozzleDistance = x;
} }

View File

@ -55,10 +55,10 @@ public class MotorCheck {
// sum += m.getTotalTime(); // sum += m.getTotalTime();
sum += m.getDiameter(); sum += m.getDiameter();
sum += m.getLength(); sum += m.getLength();
sum += m.getEmptyCG().weight; sum += m.getBurnoutMass();
sum += m.getEmptyCG().x; sum += m.getBurnoutCGx();
sum += m.getLaunchCG().weight; sum += m.getLaunchMass();
sum += m.getLaunchCG().x; sum += m.getLaunchCGx();
sum += m.getMaxThrustEstimate(); sum += m.getMaxThrustEstimate();
if (Double.isInfinite(sum) || Double.isNaN(sum)) { if (Double.isInfinite(sum) || Double.isNaN(sum)) {
System.out.println("ERROR: Invalid motor values"); System.out.println("ERROR: Invalid motor values");

View File

@ -211,7 +211,7 @@ public class MotorCompare {
min = Double.MAX_VALUE; min = Double.MAX_VALUE;
System.out.printf("Init mass :"); System.out.printf("Init mass :");
for (Motor m : motors) { for (Motor m : motors) {
double f = m.getLaunchCG().weight; double f = m.getLaunchMass();
System.out.printf("\t%.2f", f * 1000); System.out.printf("\t%.2f", f * 1000);
max = Math.max(max, f); max = Math.max(max, f);
min = Math.min(min, f); min = Math.min(min, f);
@ -229,7 +229,7 @@ public class MotorCompare {
min = Double.MAX_VALUE; min = Double.MAX_VALUE;
System.out.printf("Empty mass :"); System.out.printf("Empty mass :");
for (Motor m : motors) { for (Motor m : motors) {
double f = m.getEmptyCG().weight; double f = m.getBurnoutMass();
System.out.printf("\t%.2f", f * 1000); System.out.printf("\t%.2f", f * 1000);
max = Math.max(max, f); max = Math.max(max, f);
min = Math.min(min, f); min = Math.min(min, f);

View File

@ -8,10 +8,8 @@ import java.util.List;
import net.sf.openrocket.file.motor.GeneralMotorLoader; import net.sf.openrocket.file.motor.GeneralMotorLoader;
import net.sf.openrocket.file.motor.MotorLoader; import net.sf.openrocket.file.motor.MotorLoader;
import net.sf.openrocket.models.atmosphere.AtmosphericConditions;
import net.sf.openrocket.motor.Motor; import net.sf.openrocket.motor.Motor;
import net.sf.openrocket.motor.ThrustCurveMotor; import net.sf.openrocket.motor.ThrustCurveMotor;
import net.sf.openrocket.simulation.MotorState;
import net.sf.openrocket.util.BugException; import net.sf.openrocket.util.BugException;
import net.sf.openrocket.util.MathUtil; import net.sf.openrocket.util.MathUtil;
@ -61,29 +59,22 @@ public class MotorCorrelation {
* @return the scaled cross-correlation of the two thrust curves. * @return the scaled cross-correlation of the two thrust curves.
*/ */
public static double crossCorrelation(Motor motor1, Motor motor2) { public static double crossCorrelation(Motor motor1, Motor motor2) {
MotorState m1 = motor1.getNewInstance();
MotorState m2 = motor2.getNewInstance();
AtmosphericConditions cond = new AtmosphericConditions();
double t; double t;
double auto1 = 0; double auto1 = 0;
double auto2 = 0; double auto2 = 0;
double cross = 0; double cross = 0;
for (t = 0; t < 1000; t += 0.01) { for (t = 0; t < 1000; t += 0.01) {
m1.step(t, 0, cond);
m2.step(t, 0, cond);
double t1 = m1.getThrust(); double thrust1 = motor1.getThrustAtMotorTime( t);
double t2 = m2.getThrust(); double thrust2 = motor2.getThrustAtMotorTime( t);
if (t1 < 0 || t2 < 0) { if ( thrust1 < 0 || thrust2 < 0) {
throw new BugException("Negative thrust, t1=" + t1 + " t2=" + t2); throw new BugException("Negative thrust, t1=" + thrust1 + " t2=" + thrust2);
} }
auto1 += t1 * t1; auto1 += thrust1 * thrust1;
auto2 += t2 * t2; auto2 += thrust2 * thrust2;
cross += t1 * t2; cross += thrust1 * thrust2;
} }
double auto = Math.max(auto1, auto2); double auto = Math.max(auto1, auto2);

View File

@ -304,8 +304,8 @@ public class MassCalculatorTest extends BaseTestCase {
double expLaunchMass = 0.0164; // kg double expLaunchMass = 0.0164; // kg
double expSpentMass = 0.0131; // kg double expSpentMass = 0.0131; // kg
assertEquals(" Motor Mass "+desig+" is incorrect: ", expLaunchMass, activeMotor.getLaunchCG().weight, EPSILON); assertEquals(" Motor Mass "+desig+" is incorrect: ", expLaunchMass, activeMotor.getLaunchMass(), EPSILON);
assertEquals(" Motor Mass "+desig+" is incorrect: ", expSpentMass, activeMotor.getEmptyCG().weight, EPSILON); assertEquals(" Motor Mass "+desig+" is incorrect: ", expSpentMass, activeMotor.getBurnoutMass(), EPSILON);
// Validate Booster Launch Mass // Validate Booster Launch Mass
MassCalculator mc = new MassCalculator(); MassCalculator mc = new MassCalculator();

View File

@ -5,16 +5,13 @@ import static org.junit.Assert.assertEquals;
import org.junit.Test; import org.junit.Test;
import net.sf.openrocket.util.Coordinate; import net.sf.openrocket.util.Coordinate;
import net.sf.openrocket.util.Inertia;
public class ThrustCurveMotorTest { public class ThrustCurveMotorTest {
private final double EPS = 0.000001; // private final double EPSILON = 0.000001;
private final double radius = 0.025; private final double radius = 0.025;
private final double length = 0.10; private final double length = 0.10;
private final double longitudinal = Inertia.filledCylinderLongitudinal(radius, length);
private final double rotational = Inertia.filledCylinderRotational(radius);
private final ThrustCurveMotor motor = private final ThrustCurveMotor motor =
new ThrustCurveMotor(Manufacturer.getManufacturer("foo"), new ThrustCurveMotor(Manufacturer.getManufacturer("foo"),
@ -40,36 +37,116 @@ public class ThrustCurveMotorTest {
} }
@Test @Test
public void testInstance() { public void testTimeIndexingNegative(){
ThrustCurveMotorState instance = motor.getNewInstance(); // attempt to retrieve for a time before the motor ignites
assertEquals( 0.0, motor.getPseudoIndex( -1 ), 0.001 );
verify(instance, 0, 0.05, 0.02);
instance.step(0.0, 0, null);
verify(instance, 0, 0.05, 0.02);
instance.step(0.5, 0, null);
verify(instance, 0.5, 0.05, 0.02);
instance.step(1.5, 0, null);
verify(instance, (1.5 + 2.125)/2, 0.05, 0.02);
instance.step(2.5, 0, null);
verify(instance, (2.125 + 2.875)/2, 0.05, 0.02);
instance.step(3.0, 0, null);
verify(instance, (2+3.0/4 + 3)/2, 0.05, 0.02);
instance.step(3.5, 0, null);
verify(instance, (1.5 + 3)/2, 0.045, 0.0225);
instance.step(4.5, 0, null);
// mass and cg is simply average of the end points
verify(instance, 1.5/4, 0.035, 0.0275);
instance.step(5.0, 0, null);
verify(instance, 0, 0.03, 0.03);
} }
private void verify(ThrustCurveMotorState instance, double thrust, double mass, double cgx) { @Test
assertEquals("Testing thrust", thrust, instance.getThrust(), EPS); public void testTimeRetrieval(){
assertEquals("Testing mass", mass, instance.getCG().weight, EPS); // attempt to retrieve an integer index:
assertEquals("Testing cg x", cgx, instance.getCG().x, EPS); assertEquals( 0.0, motor.getMotorTimeAtIndex( 0 ), 0.001 );
assertEquals("Testing longitudinal inertia", mass*longitudinal, instance.getLongitudinalInertia(), EPS); assertEquals( 1.0, motor.getMotorTimeAtIndex( 1 ), 0.001 );
assertEquals("Testing rotational inertia", mass*rotational, instance.getRotationalInertia(), EPS); assertEquals( 4.0, motor.getMotorTimeAtIndex( 3 ), 0.001 );
final double searchTime = 0.2;
assertEquals( searchTime, motor.getMotorTimeAtIndex( motor.getPseudoIndex(searchTime)), 0.001 );
} }
@Test
public void testThrustRetrieval(){
// attempt to retrieve an integer index:
assertEquals( 2.0, motor.getThrustAtIndex( 1 ), 0.001 );
assertEquals( 3.0, motor.getThrustAtIndex( 2 ), 0.001 );
assertEquals( 0.0, motor.getThrustAtIndex( 3 ), 0.001 );
}
// using better interface
// @Test
// public void testCGRetrievalByDouble(){
// final double actCGx0 = motor.getCGxAtIndex( 0.0 );
// assertEquals( 0.02, actCGx0, 0.001 );
// final double actMass0 = motor.getMassAtIndex( 0.0 );
// assertEquals( 0.05, actMass0, 0.001 );
//
// final double actCGx25 = motor.getCGxAtIndex( 2.5 );
// assertEquals( 0.025, actCGx25, 0.001 );
// final double actMass25 = motor.getMassAtIndex( 2.5 );
// assertEquals( 0.04, actMass25, 0.001 );
// }
// deprecated version.
// delete this method upon change to new function signatures
@Test
public void testCGRetrieval(){
final double actCGx0 = motor.getCGAtIndex( 0.0 ).x;
assertEquals( 0.02, actCGx0, 0.001 );
final double actMass0 = motor.getCGAtIndex( 0.0 ).weight;
assertEquals( 0.05, actMass0, 0.001 );
final double actCGx25 = motor.getCGAtIndex( 2.5 ).x;
assertEquals( 0.025, actCGx25, 0.001 );
final double actMass25 = motor.getCGAtIndex( 2.5 ).weight;
assertEquals( 0.04, actMass25, 0.001 );
}
@Test
public void testTimeIndexingPastEnd(){
// attempt to retrieve for a time after motor cutoff
assertEquals( 3.0, motor.getPseudoIndex( 5.0), 0.001 );
}
@Test
public void testTimeIndexingAtEnd(){
// attempt to retrieve for a time just at motor cutoff
assertEquals( 3.0, motor.getPseudoIndex( 4.0), 0.001 );
}
@Test
public void testTimeIndexingDuring(){
// attempt to retrieve for a generic time during the motor's burn
assertEquals( 1.6, motor.getPseudoIndex( 2.2), 0.001 );
}
@Test
public void testTimeIndexingSnapUp(){
// attempt to retrieve for a generic time during the motor's burn
assertEquals( 3.0, motor.getPseudoIndex( 3.9999), 0.001 );
}
@Test
public void testTimeIndexingSnapDown(){
// attempt to retrieve for a generic time during the motor's burn
assertEquals( 2.0, motor.getPseudoIndex( 3.0001), 0.001 );
}
// @Test
// public void testInstance() {
// ThrustCurveMotorState instance = motor.getNewInstance();
//
// verify(instance, 0, 0.05, 0.02);
// instance.step(0.0, null);
// verify(instance, 0, 0.05, 0.02);
// instance.step(0.5, null);
// verify(instance, 0.5, 0.05, 0.02);
// instance.step(1.5, null);
// verify(instance, (1.5 + 2.125)/2, 0.05, 0.02);
// instance.step(2.5, null);
// verify(instance, (2.125 + 2.875)/2, 0.05, 0.02);
// instance.step(3.0, null);
// verify(instance, (2+3.0/4 + 3)/2, 0.05, 0.02);
// instance.step(3.5, null);
// verify(instance, (1.5 + 3)/2, 0.045, 0.0225);
// instance.step(4.5, null);
// // mass and cg is simply average of the end points
// verify(instance, 1.5/4, 0.035, 0.0275);
// instance.step(5.0, null);
// verify(instance, 0, 0.03, 0.03);
// }
//
// private void verify(ThrustCurveMotorState instance, double thrust, double mass, double cgx) {
// assertEquals("Testing thrust", thrust, instance.getThrust(), EPS);
// assertEquals("Testing mass", mass, instance.getCG().weight, EPS);
// assertEquals("Testing cg x", cgx, instance.getCG().x, EPS);
// assertEquals("Testing longitudinal inertia", mass*longitudinal, instance.getMotor().getLongitudinalInertia(), EPS);
// assertEquals("Testing rotational inertia", mass*rotational, instance.getMotor().getRotationalInertia(), EPS);
// }
} }

View File

@ -8,6 +8,9 @@ import java.io.InputStream;
import java.io.ObjectInputStream; import java.io.ObjectInputStream;
import java.util.List; import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import net.sf.openrocket.database.motor.ThrustCurveMotorSetDatabase; import net.sf.openrocket.database.motor.ThrustCurveMotorSetDatabase;
import net.sf.openrocket.file.iterator.DirectoryIterator; import net.sf.openrocket.file.iterator.DirectoryIterator;
import net.sf.openrocket.file.iterator.FileIterator; import net.sf.openrocket.file.iterator.FileIterator;
@ -20,9 +23,6 @@ import net.sf.openrocket.startup.Application;
import net.sf.openrocket.util.BugException; import net.sf.openrocket.util.BugException;
import net.sf.openrocket.util.Pair; import net.sf.openrocket.util.Pair;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/** /**
* An asynchronous database loader that loads the internal thrust curves * An asynchronous database loader that loads the internal thrust curves
* and external user-supplied thrust curves to a ThrustCurveMotorSetDatabase. * and external user-supplied thrust curves to a ThrustCurveMotorSetDatabase.

View File

@ -21,10 +21,9 @@ import net.sf.openrocket.rocketcomponent.FlightConfigurationId;
import net.sf.openrocket.rocketcomponent.MotorMount; import net.sf.openrocket.rocketcomponent.MotorMount;
import net.sf.openrocket.startup.Application; import net.sf.openrocket.startup.Application;
@SuppressWarnings("serial")
public class MotorChooserDialog extends JDialog implements CloseableDialog { public class MotorChooserDialog extends JDialog implements CloseableDialog {
private static final long serialVersionUID = 6903386330489783515L;
private final ThrustCurveMotorSelectionPanel selectionPanel; private final ThrustCurveMotorSelectionPanel selectionPanel;
private boolean okClicked = false; private boolean okClicked = false;

View File

@ -16,14 +16,6 @@ import javax.swing.JScrollPane;
import javax.swing.JTextArea; import javax.swing.JTextArea;
import javax.swing.SwingUtilities; import javax.swing.SwingUtilities;
import net.miginfocom.swing.MigLayout;
import net.sf.openrocket.gui.util.GUIUtil;
import net.sf.openrocket.gui.util.Icons;
import net.sf.openrocket.l10n.Translator;
import net.sf.openrocket.motor.ThrustCurveMotor;
import net.sf.openrocket.startup.Application;
import net.sf.openrocket.unit.UnitGroup;
import org.jfree.chart.ChartFactory; import org.jfree.chart.ChartFactory;
import org.jfree.chart.ChartPanel; import org.jfree.chart.ChartPanel;
import org.jfree.chart.JFreeChart; import org.jfree.chart.JFreeChart;
@ -34,6 +26,14 @@ import org.jfree.chart.title.TextTitle;
import org.jfree.data.xy.XYSeries; import org.jfree.data.xy.XYSeries;
import org.jfree.data.xy.XYSeriesCollection; import org.jfree.data.xy.XYSeriesCollection;
import net.miginfocom.swing.MigLayout;
import net.sf.openrocket.gui.util.GUIUtil;
import net.sf.openrocket.gui.util.Icons;
import net.sf.openrocket.l10n.Translator;
import net.sf.openrocket.motor.ThrustCurveMotor;
import net.sf.openrocket.startup.Application;
import net.sf.openrocket.unit.UnitGroup;
@SuppressWarnings("serial") @SuppressWarnings("serial")
class MotorInformationPanel extends JPanel { class MotorInformationPanel extends JPanel {
@ -246,9 +246,9 @@ class MotorInformationPanel extends JPanel {
burnTimeLabel.setText(UnitGroup.UNITS_SHORT_TIME.getDefaultUnit().toStringUnit( burnTimeLabel.setText(UnitGroup.UNITS_SHORT_TIME.getDefaultUnit().toStringUnit(
selectedMotor.getBurnTimeEstimate())); selectedMotor.getBurnTimeEstimate()));
launchMassLabel.setText(UnitGroup.UNITS_MASS.getDefaultUnit().toStringUnit( launchMassLabel.setText(UnitGroup.UNITS_MASS.getDefaultUnit().toStringUnit(
selectedMotor.getLaunchCG().weight)); selectedMotor.getLaunchMass()));
emptyMassLabel.setText(UnitGroup.UNITS_MASS.getDefaultUnit().toStringUnit( emptyMassLabel.setText(UnitGroup.UNITS_MASS.getDefaultUnit().toStringUnit(
selectedMotor.getEmptyCG().weight)); selectedMotor.getBurnoutMass()));
dataPointsLabel.setText("" + (selectedMotor.getTimePoints().length - 1)); dataPointsLabel.setText("" + (selectedMotor.getTimePoints().length - 1));
if (digestLabel != null) { if (digestLabel != null) {
digestLabel.setText(selectedMotor.getDigest()); digestLabel.setText(selectedMotor.getDigest());

View File

@ -151,11 +151,11 @@ enum ThrustCurveMotorColumns {
UnitGroup.UNITS_IMPULSE.getDefaultUnit() UnitGroup.UNITS_IMPULSE.getDefaultUnit()
.toStringUnit(m.getTotalImpulseEstimate()) + "<br>"); .toStringUnit(m.getTotalImpulseEstimate()) + "<br>");
tip += (trans.get("TCurveMotor.ttip.launchMass") + " " + tip += (trans.get("TCurveMotor.ttip.launchMass") + " " +
UnitGroup.UNITS_MASS.getDefaultUnit().toStringUnit(m.getLaunchCG().weight) + UnitGroup.UNITS_MASS.getDefaultUnit().toStringUnit(m.getLaunchMass()) +
"<br>"); "<br>");
tip += (trans.get("TCurveMotor.ttip.emptyMass") + " " + tip += (trans.get("TCurveMotor.ttip.emptyMass") + " " +
UnitGroup.UNITS_MASS.getDefaultUnit() UnitGroup.UNITS_MASS.getDefaultUnit()
.toStringUnit(m.getEmptyCG().weight)); .toStringUnit(m.getBurnoutMass()));
return tip; return tip;
} }

View File

@ -388,7 +388,7 @@ public class DesignReport {
motorTable.addCell(ITextHelper.createCell( motorTable.addCell(ITextHelper.createCell(
ttwFormat.format(ttw) + ":1", border)); ttwFormat.format(ttw) + ":1", border));
double propMass = (motor.getLaunchCG().weight - motor.getEmptyCG().weight); double propMass = (motor.getLaunchMass() - motor.getBurnoutMass());
motorTable.addCell(ITextHelper.createCell( motorTable.addCell(ITextHelper.createCell(
UnitGroup.UNITS_MASS.getDefaultUnit().toStringUnit(propMass), border)); UnitGroup.UNITS_MASS.getDefaultUnit().toStringUnit(propMass), border));