/** * Return the next flight event to handle, or null if no more events should be handled. This * method jumps the simulation time forward in case no motors have been ignited. The flight event * is removed from the event queue. * * @return the flight event to handle, or null */ private FlightEvent nextEvent() { EventQueue queue = currentStatus.getEventQueue(); FlightEvent event = queue.peek(); if (event == null) return null; // Jump to event if no motors have been ignited if (!currentStatus.isMotorIgnited() && event.getTime() > currentStatus.getSimulationTime()) { currentStatus.setSimulationTime(event.getTime()); } if (event.getTime() <= currentStatus.getSimulationTime()) { return queue.poll(); } else { return null; } }
/** * Handles events occurring during the flight from the event queue. Each event that has occurred * before or at the current simulation time is processed. Suitable events are also added to the * flight data. */ private boolean handleEvents() throws SimulationException { boolean ret = true; FlightEvent event; log.trace("HandleEvents: current branch = " + currentStatus.getFlightData().getBranchName()); log.trace("EventQueue = " + currentStatus.getEventQueue().toString()); for (event = nextEvent(); event != null; event = nextEvent()) { // Ignore events for components that are no longer attached to the rocket if (event.getSource() != null && event.getSource().getParent() != null && !currentStatus.getConfiguration().isComponentActive(event.getSource())) { continue; } // Call simulation listeners, allow aborting event handling if (!SimulationListenerHelper.fireHandleFlightEvent(currentStatus, event)) { continue; } if (event.getType() == FlightEvent.Type.RECOVERY_DEVICE_DEPLOYMENT) { RecoveryDevice device = (RecoveryDevice) event.getSource(); if (!SimulationListenerHelper.fireRecoveryDeviceDeployment(currentStatus, device)) { continue; } } // Check for motor ignition events, add ignition events to queue for (MotorClusterState state : currentStatus.getActiveMotors()) { if (state.testForIgnition(event)) { final double simulationTime = currentStatus.getSimulationTime(); MotorClusterState sourceState = (MotorClusterState) event.getData(); double ignitionDelay = 0; if ((event.getType() == FlightEvent.Type.BURNOUT) || (event.getType() == FlightEvent.Type.EJECTION_CHARGE)) { ignitionDelay = sourceState.getEjectionDelay(); } final double ignitionTime = currentStatus.getSimulationTime() + ignitionDelay; final RocketComponent mount = (RocketComponent) state.getMount(); // TODO: this event seems to get enqueue'd multiple times ... log.info("Queueing Ignition Event for: " + state.toDescription() + " @: " + ignitionTime); // log.info(" Because of "+event.getType().name()+" @"+event.getTime()+" from: // "+event.getSource().getName()); addEvent(new FlightEvent(FlightEvent.Type.IGNITION, ignitionTime, mount, state)); } } // Check for stage separation event for (AxialStage stage : currentStatus.getConfiguration().getActiveStages()) { int stageNo = stage.getStageNumber(); if (stageNo == 0) continue; StageSeparationConfiguration separationConfig = stage.getSeparationConfigurations().get(this.fcid); if (separationConfig.getSeparationEvent().isSeparationEvent(event, stage)) { addEvent( new FlightEvent( FlightEvent.Type.STAGE_SEPARATION, event.getTime() + separationConfig.getSeparationDelay(), stage)); } } // Check for recovery device deployment, add events to queue for (RocketComponent c : currentStatus.getConfiguration().getActiveComponents()) { if (!(c instanceof RecoveryDevice)) continue; DeploymentConfiguration deployConfig = ((RecoveryDevice) c).getDeploymentConfigurations().get(this.fcid); if (deployConfig.isActivationEvent(event, c)) { // Delay event by at least 1ms to allow stage separation to occur first addEvent( new FlightEvent( FlightEvent.Type.RECOVERY_DEVICE_DEPLOYMENT, event.getTime() + Math.max(0.001, deployConfig.getDeployDelay()), c)); } } // Handle event switch (event.getType()) { case LAUNCH: { currentStatus.getFlightData().addEvent(event); break; } case IGNITION: { MotorClusterState motorState = (MotorClusterState) event.getData(); log.info( " Igniting motor: " + motorState.toDescription() + " @" + currentStatus.getSimulationTime()); motorState.ignite(event.getTime()); // Ignite the motor currentStatus.setMotorIgnited(true); currentStatus.getFlightData().addEvent(event); // ... ignite ...uhh, again? // TBH, I'm not sure what this call is for. It seems to be mostly a bunch of event // distribution. MotorConfigurationId motorId = motorState.getID(); MotorMount mount = (MotorMount) event.getSource(); if (!SimulationListenerHelper.fireMotorIgnition( currentStatus, motorId, mount, motorState)) { continue; } // and queue up the burnout for this motor, as well. double duration = motorState.getMotor().getBurnTimeEstimate(); double burnout = currentStatus.getSimulationTime() + duration; addEvent( new FlightEvent(FlightEvent.Type.BURNOUT, burnout, event.getSource(), motorState)); break; } case LIFTOFF: { // Mark lift-off as occurred currentStatus.setLiftoff(true); currentStatus.getFlightData().addEvent(event); break; } case LAUNCHROD: { // Mark launch rod as cleared currentStatus.setLaunchRodCleared(true); currentStatus.getFlightData().addEvent(event); break; } case BURNOUT: { // If motor burnout occurs without lift-off, abort if (!currentStatus.isLiftoff()) { throw new SimulationLaunchException( trans.get("BasicEventSimulationEngine.error.earlyMotorBurnout")); } // Add ejection charge event MotorClusterState motorState = (MotorClusterState) event.getData(); motorState.burnOut(event.getTime()); AxialStage stage = motorState.getMount().getStage(); log.debug( " adding EJECTION_CHARGE event for stage " + stage.getStageNumber() + ": " + stage.getName()); log.debug( " .... for motor " + motorState.getMotor().getDesignation()); double delay = motorState.getEjectionDelay(); if (motorState.hasEjectionCharge()) { addEvent( new FlightEvent( FlightEvent.Type.EJECTION_CHARGE, currentStatus.getSimulationTime() + delay, stage, event.getData())); } currentStatus.getFlightData().addEvent(event); break; } case EJECTION_CHARGE: { MotorClusterState motorState = (MotorClusterState) event.getData(); motorState.expend(event.getTime()); currentStatus.getFlightData().addEvent(event); break; } case STAGE_SEPARATION: { // Record the event. currentStatus.getFlightData().addEvent(event); RocketComponent boosterStage = event.getSource(); final int stageNumber = boosterStage.getStageNumber(); // Mark the status as having dropped the booster currentStatus.getConfiguration().clearStage(stageNumber); // Prepare the simulation branch SimulationStatus boosterStatus = new SimulationStatus(currentStatus); boosterStatus.setFlightData( new FlightDataBranch(boosterStage.getName(), FlightDataType.TYPE_TIME)); // Mark the booster status as only having the booster. boosterStatus.getConfiguration().setOnlyStage(stageNumber); toSimulate.add(boosterStatus); log.info( String.format( "==>> @ %g; from Branch: %s ---- Branching: %s ---- \n", currentStatus.getSimulationTime(), currentStatus.getFlightData().getBranchName(), boosterStatus.getFlightData().getBranchName())); break; } case APOGEE: // Mark apogee as reached currentStatus.setApogeeReached(true); currentStatus.getFlightData().addEvent(event); // This apogee event might be the optimum if recovery has not already happened. if (currentStatus.getSimulationConditions().isCalculateExtras() && currentStatus.getDeployedRecoveryDevices().size() == 0) { currentStatus.getFlightData().setOptimumAltitude(currentStatus.getMaxAlt()); currentStatus.getFlightData().setTimeToOptimumAltitude(currentStatus.getMaxAltTime()); } break; case RECOVERY_DEVICE_DEPLOYMENT: RocketComponent c = event.getSource(); int n = c.getStageNumber(); // Ignore event if stage not active if (currentStatus.getConfiguration().isStageActive(n)) { // TODO: HIGH: Check stage activeness for other events as well? // Check whether any motor in the active stages is active anymore for (MotorClusterState state : currentStatus.getActiveMotors()) { if (state.isSpent()) { continue; } currentStatus.getWarnings().add(Warning.RECOVERY_DEPLOYMENT_WHILE_BURNING); } // Check for launch rod if (!currentStatus.isLaunchRodCleared()) { currentStatus.getWarnings().add(Warning.RECOVERY_LAUNCH_ROD); } // Check current velocity if (currentStatus.getRocketVelocity().length() > 20) { currentStatus .getWarnings() .add(new Warning.HighSpeedDeployment(currentStatus.getRocketVelocity().length())); } currentStatus.setLiftoff(true); currentStatus.getDeployedRecoveryDevices().add((RecoveryDevice) c); // If we haven't already reached apogee, then we need to compute the actual coast time // to determine the optimum altitude. if (currentStatus.getSimulationConditions().isCalculateExtras() && !currentStatus.isApogeeReached()) { FlightData coastStatus = computeCoastTime(); currentStatus.getFlightData().setOptimumAltitude(coastStatus.getMaxAltitude()); currentStatus.getFlightData().setTimeToOptimumAltitude(coastStatus.getTimeToApogee()); } this.currentStepper = this.landingStepper; this.currentStatus = currentStepper.initialize(currentStatus); currentStatus.getFlightData().addEvent(event); } break; case GROUND_HIT: currentStatus.getFlightData().addEvent(event); break; case SIMULATION_END: ret = false; currentStatus.getFlightData().addEvent(event); break; case ALTITUDE: log.trace("BasicEventSimulationEngine: Handling event " + event); break; case TUMBLE: this.currentStepper = this.tumbleStepper; this.currentStatus = currentStepper.initialize(currentStatus); currentStatus.getFlightData().addEvent(event); break; } } if (1200 < currentStatus.getSimulationTime()) { ret = false; log.error("Simulation hit max time (1200s): aborting."); currentStatus .getFlightData() .addEvent( new FlightEvent(FlightEvent.Type.SIMULATION_END, currentStatus.getSimulationTime())); } // If no motor has ignited, abort if (!currentStatus.isMotorIgnited()) { throw new MotorIgnitionException(trans.get("BasicEventSimulationEngine.error.noIgnition")); } return ret; }
@Override public FlightData simulate(SimulationConditions simulationConditions) throws SimulationException { Set<MotorId> motorBurntOut = new HashSet<MotorId>(); // Set up flight data FlightData flightData = new FlightData(); // Set up rocket configuration Configuration configuration = setupConfiguration(simulationConditions); MotorInstanceConfiguration motorConfiguration = setupMotorConfiguration(configuration); if (motorConfiguration.getMotorIDs().isEmpty()) { throw new MotorIgnitionException("No motors defined in the simulation."); } // Initialize the simulation currentStepper = flightStepper; status = initialStatus(configuration, motorConfiguration, simulationConditions, flightData); status = currentStepper.initialize(status); SimulationListenerHelper.fireStartSimulation(status); // Get originating position (in case listener has modified launch position) Coordinate origin = status.getRocketPosition(); Coordinate originVelocity = status.getRocketVelocity(); try { double maxAlt = Double.NEGATIVE_INFINITY; // Start the simulation while (handleEvents()) { // Take the step double oldAlt = status.getRocketPosition().z; if (SimulationListenerHelper.firePreStep(status)) { // Step at most to the next event double maxStepTime = Double.MAX_VALUE; FlightEvent nextEvent = status.getEventQueue().peek(); if (nextEvent != null) { maxStepTime = MathUtil.max(nextEvent.getTime() - status.getSimulationTime(), 0.001); } log.verbose( "BasicEventSimulationEngine: Taking simulation step at t=" + status.getSimulationTime()); currentStepper.step(status, maxStepTime); } SimulationListenerHelper.firePostStep(status); // Calculate values for custom expressions FlightDataBranch data = status.getFlightData(); ArrayList<CustomExpression> allExpressions = status.getSimulationConditions().getSimulation().getCustomExpressions(); for (CustomExpression expression : allExpressions) { data.setValue(expression.getType(), expression.evaluate(status)); } // Check for NaN values in the simulation status checkNaN(); // Add altitude event addEvent( new FlightEvent( FlightEvent.Type.ALTITUDE, status.getSimulationTime(), status.getConfiguration().getRocket(), new Pair<Double, Double>(oldAlt, status.getRocketPosition().z))); if (status.getRocketPosition().z > maxAlt) { maxAlt = status.getRocketPosition().z; } // Position relative to start location Coordinate relativePosition = status.getRocketPosition().sub(origin); // Add appropriate events if (!status.isLiftoff()) { // Avoid sinking into ground before liftoff if (relativePosition.z < 0) { status.setRocketPosition(origin); status.setRocketVelocity(originVelocity); } // Detect lift-off if (relativePosition.z > 0.02) { addEvent(new FlightEvent(FlightEvent.Type.LIFTOFF, status.getSimulationTime())); } } else { // Check ground hit after liftoff if (status.getRocketPosition().z < 0) { status.setRocketPosition(status.getRocketPosition().setZ(0)); addEvent(new FlightEvent(FlightEvent.Type.GROUND_HIT, status.getSimulationTime())); addEvent(new FlightEvent(FlightEvent.Type.SIMULATION_END, status.getSimulationTime())); } } // Check for launch guide clearance if (!status.isLaunchRodCleared() && relativePosition.length() > status.getSimulationConditions().getLaunchRodLength()) { addEvent(new FlightEvent(FlightEvent.Type.LAUNCHROD, status.getSimulationTime(), null)); } // Check for apogee if (!status.isApogeeReached() && status.getRocketPosition().z < maxAlt - 0.01) { addEvent( new FlightEvent( FlightEvent.Type.APOGEE, status.getSimulationTime(), status.getConfiguration().getRocket())); } // Check for burnt out motors for (MotorId motorId : status.getMotorConfiguration().getMotorIDs()) { MotorInstance motor = status.getMotorConfiguration().getMotorInstance(motorId); if (!motor.isActive() && motorBurntOut.add(motorId)) { addEvent( new FlightEvent( FlightEvent.Type.BURNOUT, status.getSimulationTime(), (RocketComponent) status.getMotorConfiguration().getMotorMount(motorId), motorId)); } } } } catch (SimulationException e) { SimulationListenerHelper.fireEndSimulation(status, e); throw e; } SimulationListenerHelper.fireEndSimulation(status, null); flightData.addBranch(status.getFlightData()); if (!flightData.getWarningSet().isEmpty()) { log.info("Warnings at the end of simulation: " + flightData.getWarningSet()); } // TODO: HIGH: Simulate branches return flightData; }
private FlightDataBranch simulateLoop() { // Initialize the simulation currentStepper = flightStepper; currentStatus = currentStepper.initialize(currentStatus); // Get originating position (in case listener has modified launch position) Coordinate origin = currentStatus.getRocketPosition(); Coordinate originVelocity = currentStatus.getRocketVelocity(); try { // Start the simulation while (handleEvents()) { // Take the step double oldAlt = currentStatus.getRocketPosition().z; if (SimulationListenerHelper.firePreStep(currentStatus)) { // Step at most to the next event double maxStepTime = Double.MAX_VALUE; FlightEvent nextEvent = currentStatus.getEventQueue().peek(); if (nextEvent != null) { maxStepTime = MathUtil.max(nextEvent.getTime() - currentStatus.getSimulationTime(), 0.001); } log.trace( "BasicEventSimulationEngine: Taking simulation step at t=" + currentStatus.getSimulationTime()); currentStepper.step(currentStatus, maxStepTime); } SimulationListenerHelper.firePostStep(currentStatus); // Check for NaN values in the simulation status checkNaN(); // Add altitude event addEvent( new FlightEvent( FlightEvent.Type.ALTITUDE, currentStatus.getSimulationTime(), currentStatus.getConfiguration().getRocket(), new Pair<Double, Double>(oldAlt, currentStatus.getRocketPosition().z))); if (currentStatus.getRocketPosition().z > currentStatus.getMaxAlt()) { currentStatus.setMaxAlt(currentStatus.getRocketPosition().z); } // Position relative to start location Coordinate relativePosition = currentStatus.getRocketPosition().sub(origin); // Add appropriate events if (!currentStatus.isLiftoff()) { // Avoid sinking into ground before liftoff if (relativePosition.z < 0) { currentStatus.setRocketPosition(origin); currentStatus.setRocketVelocity(originVelocity); } // Detect lift-off if (relativePosition.z > 0.02) { addEvent(new FlightEvent(FlightEvent.Type.LIFTOFF, currentStatus.getSimulationTime())); } } else { // Check ground hit after liftoff if (currentStatus.getRocketPosition().z < 0) { currentStatus.setRocketPosition(currentStatus.getRocketPosition().setZ(0)); addEvent( new FlightEvent(FlightEvent.Type.GROUND_HIT, currentStatus.getSimulationTime())); addEvent( new FlightEvent( FlightEvent.Type.SIMULATION_END, currentStatus.getSimulationTime())); } } // Check for launch guide clearance if (!currentStatus.isLaunchRodCleared() && relativePosition.length() > currentStatus.getSimulationConditions().getLaunchRodLength()) { addEvent( new FlightEvent(FlightEvent.Type.LAUNCHROD, currentStatus.getSimulationTime(), null)); } // Check for apogee if (!currentStatus.isApogeeReached() && currentStatus.getRocketPosition().z < currentStatus.getMaxAlt() - 0.01) { currentStatus.setMaxAltTime(currentStatus.getSimulationTime()); addEvent( new FlightEvent( FlightEvent.Type.APOGEE, currentStatus.getSimulationTime(), currentStatus.getConfiguration().getRocket())); } // //@Obsolete // //@Redundant // // Check for burnt out motors // for( MotorClusterState state : currentStatus.getActiveMotors()){ // if ( state.isSpent()){ // addEvent(new FlightEvent(FlightEvent.Type.BURNOUT, // currentStatus.getSimulationTime(), // (RocketComponent) state.getMount(), state)); // } // } // Check for Tumbling // Conditions for transision are: // apogee reached (if sustainer stage) // and is not already tumbling // and not stable (cg > cp) // and aoa > AOA_TUMBLE_CONDITION threshold // and thrust < THRUST_TUMBLE_CONDITION threshold if (!currentStatus.isTumbling()) { final double t = currentStatus.getFlightData().getLast(FlightDataType.TYPE_THRUST_FORCE); final double cp = currentStatus.getFlightData().getLast(FlightDataType.TYPE_CP_LOCATION); final double cg = currentStatus.getFlightData().getLast(FlightDataType.TYPE_CG_LOCATION); final double aoa = currentStatus.getFlightData().getLast(FlightDataType.TYPE_AOA); final boolean wantToTumble = (cg > cp && aoa > AOA_TUMBLE_CONDITION); if (wantToTumble) { final boolean tooMuchThrust = t > THRUST_TUMBLE_CONDITION; // final boolean isSustainer = status.getConfiguration().isStageActive(0); final boolean isApogee = currentStatus.isApogeeReached(); if (tooMuchThrust) { currentStatus.getWarnings().add(Warning.TUMBLE_UNDER_THRUST); } else if (isApogee) { addEvent(new FlightEvent(FlightEvent.Type.TUMBLE, currentStatus.getSimulationTime())); currentStatus.setTumbling(true); } } } } } catch (SimulationException e) { SimulationListenerHelper.fireEndSimulation(currentStatus, e); // Add FlightEvent for Abort. currentStatus .getFlightData() .addEvent( new FlightEvent( FlightEvent.Type.EXCEPTION, currentStatus.getSimulationTime(), currentStatus.getConfiguration().getRocket(), e.getLocalizedMessage())); currentStatus.getWarnings().add(e.getLocalizedMessage()); } return currentStatus.getFlightData(); }
/** * Handles events occurring during the flight from the event queue. Each event that has occurred * before or at the current simulation time is processed. Suitable events are also added to the * flight data. */ private boolean handleEvents() throws SimulationException { boolean ret = true; FlightEvent event; for (event = nextEvent(); event != null; event = nextEvent()) { // Ignore events for components that are no longer attached to the rocket if (event.getSource() != null && event.getSource().getParent() != null && !status.getConfiguration().isStageActive(event.getSource().getStageNumber())) { continue; } // Call simulation listeners, allow aborting event handling if (!SimulationListenerHelper.fireHandleFlightEvent(status, event)) { continue; } if (event.getType() != FlightEvent.Type.ALTITUDE) { log.verbose("BasicEventSimulationEngine: Handling event " + event); } if (event.getType() == FlightEvent.Type.IGNITION) { MotorMount mount = (MotorMount) event.getSource(); MotorId motorId = (MotorId) event.getData(); MotorInstance instance = status.getMotorConfiguration().getMotorInstance(motorId); if (!SimulationListenerHelper.fireMotorIgnition(status, motorId, mount, instance)) { continue; } } if (event.getType() == FlightEvent.Type.RECOVERY_DEVICE_DEPLOYMENT) { RecoveryDevice device = (RecoveryDevice) event.getSource(); if (!SimulationListenerHelper.fireRecoveryDeviceDeployment(status, device)) { continue; } } // Check for motor ignition events, add ignition events to queue for (MotorId id : status.getMotorConfiguration().getMotorIDs()) { MotorMount mount = status.getMotorConfiguration().getMotorMount(id); RocketComponent component = (RocketComponent) mount; if (mount.getIgnitionEvent().isActivationEvent(event, component)) { addEvent( new FlightEvent( FlightEvent.Type.IGNITION, status.getSimulationTime() + mount.getIgnitionDelay(), component, id)); } } // Check for stage separation event for (int stageNo : status.getConfiguration().getActiveStages()) { if (stageNo == 0) continue; Stage stage = (Stage) status.getConfiguration().getRocket().getChild(stageNo); if (stage.getSeparationEvent().isSeparationEvent(event, stage)) { addEvent( new FlightEvent( FlightEvent.Type.STAGE_SEPARATION, event.getTime() + stage.getSeparationDelay(), stage)); } } // Check for recovery device deployment, add events to queue Iterator<RocketComponent> rci = status.getConfiguration().iterator(); while (rci.hasNext()) { RocketComponent c = rci.next(); if (!(c instanceof RecoveryDevice)) continue; if (((RecoveryDevice) c).getDeployEvent().isActivationEvent(event, c)) { // Delay event by at least 1ms to allow stage separation to occur first addEvent( new FlightEvent( FlightEvent.Type.RECOVERY_DEVICE_DEPLOYMENT, event.getTime() + Math.max(0.001, ((RecoveryDevice) c).getDeployDelay()), c)); } } // Handle event switch (event.getType()) { case LAUNCH: { status.getFlightData().addEvent(event); break; } case IGNITION: { // Ignite the motor MotorMount mount = (MotorMount) event.getSource(); RocketComponent component = (RocketComponent) mount; MotorId motorId = (MotorId) event.getData(); MotorInstanceConfiguration config = status.getMotorConfiguration(); config.setMotorIgnitionTime(motorId, event.getTime()); status.setMotorIgnited(true); status.getFlightData().addEvent(event); break; } case LIFTOFF: { // Mark lift-off as occurred status.setLiftoff(true); status.getFlightData().addEvent(event); break; } case LAUNCHROD: { // Mark launch rod as cleared status.setLaunchRodCleared(true); status.getFlightData().addEvent(event); break; } case BURNOUT: { // If motor burnout occurs without lift-off, abort if (!status.isLiftoff()) { throw new SimulationLaunchException("Motor burnout without liftoff."); } // Add ejection charge event String id = status.getConfiguration().getMotorConfigurationID(); MotorMount mount = (MotorMount) event.getSource(); double delay = mount.getMotorDelay(id); if (delay != Motor.PLUGGED) { addEvent( new FlightEvent( FlightEvent.Type.EJECTION_CHARGE, status.getSimulationTime() + delay, event.getSource(), event.getData())); } status.getFlightData().addEvent(event); break; } case EJECTION_CHARGE: { status.getFlightData().addEvent(event); break; } case STAGE_SEPARATION: { // TODO: HIGH: Store lower stages to be simulated later RocketComponent stage = event.getSource(); int n = stage.getStageNumber(); status.getConfiguration().setToStage(n - 1); status.getFlightData().addEvent(event); break; } case APOGEE: // Mark apogee as reached status.setApogeeReached(true); status.getFlightData().addEvent(event); break; case RECOVERY_DEVICE_DEPLOYMENT: RocketComponent c = event.getSource(); int n = c.getStageNumber(); // Ignore event if stage not active if (status.getConfiguration().isStageActive(n)) { // TODO: HIGH: Check stage activeness for other events as well? // Check whether any motor in the active stages is active anymore for (MotorId motorId : status.getMotorConfiguration().getMotorIDs()) { int stage = ((RocketComponent) status.getMotorConfiguration().getMotorMount(motorId)) .getStageNumber(); if (!status.getConfiguration().isStageActive(stage)) continue; if (!status.getMotorConfiguration().getMotorInstance(motorId).isActive()) continue; status.getWarnings().add(Warning.RECOVERY_DEPLOYMENT_WHILE_BURNING); } // Check for launch rod if (!status.isLaunchRodCleared()) { status .getWarnings() .add( Warning.fromString( "Recovery device device deployed while on " + "the launch guide.")); } // Check current velocity if (status.getRocketVelocity().length() > 20) { // TODO: LOW: Custom warning. status .getWarnings() .add( Warning.fromString( "Recovery device deployment at high " + "speed (" + UnitGroup.UNITS_VELOCITY.toStringUnit( status.getRocketVelocity().length()) + ").")); } status.setLiftoff(true); status.getDeployedRecoveryDevices().add((RecoveryDevice) c); this.currentStepper = this.landingStepper; this.status = currentStepper.initialize(status); status.getFlightData().addEvent(event); } break; case GROUND_HIT: status.getFlightData().addEvent(event); break; case SIMULATION_END: ret = false; status.getFlightData().addEvent(event); break; case ALTITUDE: break; } } // If no motor has ignited, abort if (!status.isMotorIgnited()) { throw new MotorIgnitionException("No motors ignited."); } return ret; }