diff --git a/core/src/main/java/info/openrocket/core/simulation/AbstractEulerStepper.java b/core/src/main/java/info/openrocket/core/simulation/AbstractEulerStepper.java index 1dcb406fb..d1042183e 100644 --- a/core/src/main/java/info/openrocket/core/simulation/AbstractEulerStepper.java +++ b/core/src/main/java/info/openrocket/core/simulation/AbstractEulerStepper.java @@ -70,22 +70,24 @@ public abstract class AbstractEulerStepper extends AbstractSimulationStepper { } // Compute drag acceleration - store.linearAcceleration = airSpeed.normalize().multiply(-store.dragForce / store.rocketMass.getMass()); + Coordinate linearAcceleration = airSpeed.normalize().multiply(-store.dragForce / store.rocketMass.getMass()); // Add effect of gravity store.gravity = modelGravity(status); - store.linearAcceleration = store.linearAcceleration.sub(0, 0, store.gravity); + linearAcceleration = linearAcceleration.sub(0, 0, store.gravity); // Add coriolis acceleration store.coriolisAcceleration = status.getSimulationConditions().getGeodeticComputation().getCoriolisAcceleration( status.getRocketWorldPosition(), status.getRocketVelocity()); - store.linearAcceleration = store.linearAcceleration.add(store.coriolisAcceleration); + linearAcceleration = linearAcceleration.add(store.coriolisAcceleration); + + store.accelerationData = new AccelerationData(null, null, linearAcceleration, Coordinate.NUL, status.getRocketOrientationQuaternion()); // Select tentative time step store.timeStep = RECOVERY_TIME_STEP; // adjust based on acceleration - final double absAccel = store.linearAcceleration.length(); + final double absAccel = linearAcceleration.length(); if (absAccel > MathUtil.EPSILON) { store.timeStep = Math.min(store.timeStep, 1.0/absAccel); } @@ -105,7 +107,7 @@ public abstract class AbstractEulerStepper extends AbstractSimulationStepper { log.trace("timeStep is " + store.timeStep); // Perform Euler integration - EulerValues newVals = eulerIntegrate(status.getRocketPosition(), status.getRocketVelocity(), store.linearAcceleration, store.timeStep); + EulerValues newVals = eulerIntegrate(status.getRocketPosition(), status.getRocketVelocity(), linearAcceleration, store.timeStep); // Check to see if z or either of its first two derivatives have changed sign and recalculate // time step to point of change if so @@ -115,7 +117,7 @@ public abstract class AbstractEulerStepper extends AbstractSimulationStepper { // Note that it's virtually impossible for apogee to occur on the same // step as either ground hit or descent rate inflection, and if we get a ground hit // any descent rate inflection won't matter - final double a = store.linearAcceleration.z; + final double a = linearAcceleration.z; final double v = status.getRocketVelocity().z; final double z = status.getRocketPosition().z; double t = store.timeStep; @@ -136,11 +138,11 @@ public abstract class AbstractEulerStepper extends AbstractSimulationStepper { // dA/dT = dA/dV * dV/dT final double dFdV = CdA * store.atmosphericConditions.getDensity() * airSpeed.length(); final Coordinate dAdV = airSpeed.normalize().multiply(dFdV / store.rocketMass.getMass()); - final Coordinate jerk = store.linearAcceleration.multiply(dAdV); - final Coordinate newAcceleration = store.linearAcceleration.add(jerk.multiply(store.timeStep)); + final Coordinate jerk = linearAcceleration.multiply(dAdV); + final Coordinate newAcceleration = linearAcceleration.add(jerk.multiply(store.timeStep)); // Only do this one if acceleration is appreciably different from 0 - if (newAcceleration.z * store.linearAcceleration.z < -MathUtil.EPSILON) { + if (newAcceleration.z * linearAcceleration.z < -MathUtil.EPSILON) { // If acceleration oscillation is building up, the new timestep is the solution of // a + j*t = 0 t = Math.abs(a / jerk.z); @@ -159,7 +161,7 @@ public abstract class AbstractEulerStepper extends AbstractSimulationStepper { store.timeStep = maxTimeStep; } - newVals = eulerIntegrate(status.getRocketPosition(), status.getRocketVelocity(), store.linearAcceleration, store.timeStep); + newVals = eulerIntegrate(status.getRocketPosition(), status.getRocketVelocity(), linearAcceleration, store.timeStep); // If we just landed chop off rounding error if (Math.abs(newVals.pos.z) < MathUtil.EPSILON) { @@ -171,7 +173,6 @@ public abstract class AbstractEulerStepper extends AbstractSimulationStepper { status.setRocketPosition(newVals.pos); status.setRocketVelocity(newVals.vel); - status.setRocketAcceleration(store.linearAcceleration); // Update the world coordinate WorldCoordinate w = status.getSimulationConditions().getLaunchSite(); diff --git a/core/src/main/java/info/openrocket/core/simulation/AbstractSimulationStepper.java b/core/src/main/java/info/openrocket/core/simulation/AbstractSimulationStepper.java index 062536233..474d6c474 100644 --- a/core/src/main/java/info/openrocket/core/simulation/AbstractSimulationStepper.java +++ b/core/src/main/java/info/openrocket/core/simulation/AbstractSimulationStepper.java @@ -163,14 +163,12 @@ public abstract class AbstractSimulationStepper implements SimulationStepper { * * @param status the current simulation status. * @param timestep the time step of the current iteration. - * @param acceleration the current (approximate) acceleration - * @param atmosphericConditions the current atmospheric conditions + * @param store the simulation calculation DataStore (contains acceleration, atmosphere) * @param stepMotors whether to step the motors forward or work on a clone object * @return the average thrust during the time step. */ - protected double calculateThrust(SimulationStatus status, - double acceleration, AtmosphericConditions atmosphericConditions, - boolean stepMotors) throws SimulationException { + protected double calculateThrust(SimulationStatus status, DataStore store, + boolean stepMotors) throws SimulationException { double thrust; // Pre-listeners @@ -240,16 +238,11 @@ public abstract class AbstractSimulationStepper implements SimulationStepper { public FlightConditions flightConditions; - public double longitudinalAcceleration = Double.NaN; - public RigidBody rocketMass; public RigidBody motorMass; public Coordinate coriolisAcceleration; - - public Coordinate linearAcceleration; - public Coordinate angularAcceleration; public Coordinate launchRodDirection = null; @@ -264,9 +257,6 @@ public abstract class AbstractSimulationStepper implements SimulationStepper { public double dragForce = Double.NaN; public double lateralPitchRate = Double.NaN; - public double rollAcceleration = Double.NaN; - public double lateralPitchAcceleration = Double.NaN; - public Rotation2D thetaRotation; void storeData(SimulationStatus status) { @@ -286,12 +276,12 @@ public abstract class AbstractSimulationStepper implements SimulationStepper { dataBranch.setValue(FlightDataType.TYPE_CORIOLIS_ACCELERATION, coriolisAcceleration.length()); } - if (null != linearAcceleration) { + if (null != accelerationData) { dataBranch.setValue(FlightDataType.TYPE_ACCELERATION_XY, - MathUtil.hypot(linearAcceleration.x, linearAcceleration.y)); + MathUtil.hypot(accelerationData.getLinearAccelerationWC().x, accelerationData.getLinearAccelerationWC().y)); - dataBranch.setValue(FlightDataType.TYPE_ACCELERATION_TOTAL, linearAcceleration.length()); - dataBranch.setValue(FlightDataType.TYPE_ACCELERATION_Z, linearAcceleration.z); + dataBranch.setValue(FlightDataType.TYPE_ACCELERATION_TOTAL, accelerationData.getLinearAccelerationWC().length()); + dataBranch.setValue(FlightDataType.TYPE_ACCELERATION_Z, accelerationData.getLinearAccelerationWC().z); } if (null != rocketMass) { diff --git a/core/src/main/java/info/openrocket/core/simulation/RK4SimulationStepper.java b/core/src/main/java/info/openrocket/core/simulation/RK4SimulationStepper.java index 91b76d28d..889e74d17 100644 --- a/core/src/main/java/info/openrocket/core/simulation/RK4SimulationStepper.java +++ b/core/src/main/java/info/openrocket/core/simulation/RK4SimulationStepper.java @@ -57,7 +57,7 @@ public class RK4SimulationStepper extends AbstractSimulationStepper { // private static final double MAX_ROLL_STEP_ANGLE = 8.32 * Math.PI/180; private static final double MAX_ROLL_RATE_CHANGE = 2 * Math.PI / 180; - private static final double MAX_PITCH_CHANGE = 4 * Math.PI / 180; + private static final double MAX_PITCH_YAW_CHANGE = 4 * Math.PI / 180; private Random random; DataStore store = new DataStore(); @@ -133,8 +133,10 @@ public class RK4SimulationStepper extends AbstractSimulationStepper { dt[1] = maxTimeStep; dt[2] = status.getSimulationConditions().getMaximumAngleStep() / store.lateralPitchRate; dt[3] = Math.abs(MAX_ROLL_STEP_ANGLE / store.flightConditions.getRollRate()); - dt[4] = Math.abs(MAX_ROLL_RATE_CHANGE / store.rollAcceleration); - dt[5] = Math.abs(MAX_PITCH_CHANGE / store.lateralPitchAcceleration); + dt[4] = Math.abs(MAX_ROLL_RATE_CHANGE / store.accelerationData.getRotationalAccelerationRC().z); + dt[5] = Math.abs(MAX_PITCH_YAW_CHANGE / + MathUtil.max(Math.abs(store.accelerationData.getRotationalAccelerationRC().x), + Math.abs(store.accelerationData.getRotationalAccelerationRC().y))); if (!status.isLaunchRodCleared()) { dt[0] /= 5.0; dt[6] = status.getSimulationConditions().getLaunchRodLength() / k1.v.length() / 10; @@ -273,8 +275,8 @@ public class RK4SimulationStepper extends AbstractSimulationStepper { // Call post-listeners store.accelerationData = SimulationListenerHelper.firePostAccelerationCalculation(status, store.accelerationData); - params.a = dataStore.linearAcceleration; - params.ra = dataStore.angularAcceleration; + params.a = dataStore.accelerationData.getLinearAccelerationWC(); + params.ra = dataStore.accelerationData.getRotationalAccelerationWC(); params.v = status.getRocketVelocity(); params.rv = status.getRocketRotationVelocity(); @@ -298,7 +300,9 @@ public class RK4SimulationStepper extends AbstractSimulationStepper { * @throws SimulationException */ private AccelerationData calculateAcceleration(SimulationStatus status, DataStore store) throws SimulationException { - + Coordinate linearAcceleration; + Coordinate angularAcceleration; + // Compute the forces affecting the rocket calculateForces(status, store); @@ -325,35 +329,33 @@ public class RK4SimulationStepper extends AbstractSimulationStepper { double fN = store.forces.getCN() * dynP * refArea; double fSide = store.forces.getCside() * dynP * refArea; - store.thrustForce = calculateThrust(status, store.longitudinalAcceleration, store.flightConditions.getAtmosphericConditions(), false); + store.thrustForce = calculateThrust(status, store, false); double forceZ = store.thrustForce - store.dragForce; - store.linearAcceleration = new Coordinate(-fN / store.rocketMass.getMass(), + linearAcceleration = new Coordinate(-fN / store.rocketMass.getMass(), -fSide / store.rocketMass.getMass(), forceZ / store.rocketMass.getMass()); - store.linearAcceleration = store.thetaRotation.rotateZ(store.linearAcceleration); + linearAcceleration = store.thetaRotation.rotateZ(linearAcceleration); // Convert into rocket world coordinates - store.linearAcceleration = status.getRocketOrientationQuaternion().rotate(store.linearAcceleration); + linearAcceleration = status.getRocketOrientationQuaternion().rotate(linearAcceleration); // add effect of gravity store.gravity = modelGravity(status); - store.linearAcceleration = store.linearAcceleration.sub(0, 0, store.gravity); + linearAcceleration = linearAcceleration.sub(0, 0, store.gravity); // add effect of Coriolis acceleration store.coriolisAcceleration = status.getSimulationConditions().getGeodeticComputation() .getCoriolisAcceleration(status.getRocketWorldPosition(), status.getRocketVelocity()); - store.linearAcceleration = store.linearAcceleration.add(store.coriolisAcceleration); + linearAcceleration = linearAcceleration.add(store.coriolisAcceleration); // If still on the launch rod, project acceleration onto launch rod direction and // set angular acceleration to zero. if (!status.isLaunchRodCleared()) { - store.linearAcceleration = store.launchRodDirection.multiply(store.linearAcceleration.dot(store.launchRodDirection)); - store.angularAcceleration = Coordinate.NUL; - store.rollAcceleration = 0; - store.lateralPitchAcceleration = 0; + linearAcceleration = store.launchRodDirection.multiply(linearAcceleration.dot(store.launchRodDirection)); + angularAcceleration = Coordinate.NUL; } else { @@ -366,24 +368,18 @@ public class RK4SimulationStepper extends AbstractSimulationStepper { double momY = Cm * dynP * refArea * refLength; double momZ = store.forces.getCroll() * dynP * refArea * refLength; - // Compute acceleration in rocket coordinates - store.angularAcceleration = new Coordinate(momX / store.rocketMass.getLongitudinalInertia(), + // Compute angular acceleration in rocket coordinates + angularAcceleration = new Coordinate(momX / store.rocketMass.getLongitudinalInertia(), momY / store.rocketMass.getLongitudinalInertia(), momZ / store.rocketMass.getRotationalInertia()); - store.rollAcceleration = store.angularAcceleration.z; - // TODO: LOW: This should be hypot, but does it matter? - store.lateralPitchAcceleration = MathUtil.max(Math.abs(store.angularAcceleration.x), - Math.abs(store.angularAcceleration.y)); - - store.angularAcceleration = store.thetaRotation.rotateZ(store.angularAcceleration); + angularAcceleration = store.thetaRotation.rotateZ(angularAcceleration); // Convert to world coordinates - store.angularAcceleration = status.getRocketOrientationQuaternion().rotate(store.angularAcceleration); - + angularAcceleration = status.getRocketOrientationQuaternion().rotate(angularAcceleration); } - return new AccelerationData(null, null, store.linearAcceleration, store.angularAcceleration, status.getRocketOrientationQuaternion()); + return new AccelerationData(null, null, linearAcceleration, angularAcceleration, status.getRocketOrientationQuaternion()); } diff --git a/swing/build.gradle b/swing/build.gradle index 1d0736c53..6a0b167c2 100644 --- a/swing/build.gradle +++ b/swing/build.gradle @@ -80,9 +80,9 @@ dependencies { implementation group: 'io.github.g00fy2', name: 'versioncompare', version: '1.5.0' implementation 'com.github.Dansoftowner:jSystemThemeDetector:3.8' - implementation group: 'com.formdev', name: 'flatlaf', version: '3.4.1' - implementation group: 'com.formdev', name: 'flatlaf-extras', version: '3.4.1' - implementation group: 'com.formdev', name: 'flatlaf-intellij-themes', version: '3.4.1' + implementation group: 'com.formdev', name: 'flatlaf', version: '3.5' + implementation group: 'com.formdev', name: 'flatlaf-extras', version: '3.5' + implementation group: 'com.formdev', name: 'flatlaf-intellij-themes', version: '3.5' implementation group: 'ch.qos.logback', name: 'logback-core', version: '1.5.0' implementation group: 'ch.qos.logback', name: 'logback-classic', version: '1.5.0' diff --git a/swing/src/main/java/info/openrocket/swing/gui/scalefigure/RocketPanel.java b/swing/src/main/java/info/openrocket/swing/gui/scalefigure/RocketPanel.java index 070c85bbb..5437348c2 100644 --- a/swing/src/main/java/info/openrocket/swing/gui/scalefigure/RocketPanel.java +++ b/swing/src/main/java/info/openrocket/swing/gui/scalefigure/RocketPanel.java @@ -217,12 +217,27 @@ public class RocketPanel extends JPanel implements TreeSelectionListener, Change scrollPane = new ScaleScrollPane(figure) { private static final long serialVersionUID = 1L; final CustomClickCountListener clickCountListener = new CustomClickCountListener(); + private Point mousePressedLoc = null; + private double originalFigureRotation = 0; @Override public void mouseClicked(MouseEvent event) { clickCountListener.click(); handleMouseClick(event, clickCountListener.getClickCount()); } + + public void mousePressed(MouseEvent e) { + if (is3d) { + return; + } + mousePressedLoc = e.getPoint(); + originalFigureRotation = figure.getRotation(); + } + + @Override + public void mouseDragged(MouseEvent e) { + handleMouseDragged(e, mousePressedLoc, originalFigureRotation); + } }; scrollPane.getViewport().setScrollMode(JViewport.SIMPLE_SCROLL_MODE); scrollPane.setFitting(true); @@ -413,6 +428,7 @@ public class RocketPanel extends JPanel implements TreeSelectionListener, Change // Create slider and scroll pane rotationModel = new DoubleModel(figure, "Rotation", UnitGroup.UNITS_ANGLE, 0, 2 * Math.PI); + figure.addChangeListener(rotationModel); UnitSelector us = new UnitSelector(rotationModel, true); us.setHorizontalAlignment(JLabel.CENTER); add(us, "alignx 50%, growx"); @@ -422,12 +438,13 @@ public class RocketPanel extends JPanel implements TreeSelectionListener, Change add(figureHolder, "grow, spany 2, wmin 300lp, hmin 100lp, wrap"); // Add rotation slider - // Dummy label to find the minimum size to fit "360deg" - JLabel l = new JLabel("360" + Chars.DEGREE); + // Dummy label to find the minimum size to fit "360.0deg" + JLabel l = new JLabel("360.0" + Chars.DEGREE); Dimension d = l.getPreferredSize(); - add(rotationSlider = new BasicSlider(rotationModel.getSliderModel(0, 2 * Math.PI), JSlider.VERTICAL, true), - "ax 50%, wrap, width " + (d.width + 6) + "px:null:null, growy"); + us.setMinimumSize(new Dimension(d.width, us.getPreferredSize().height)); + rotationSlider = new BasicSlider(rotationModel.getSliderModel(0, 2 * Math.PI), JSlider.VERTICAL, true); + add(rotationSlider, "ax 50%, wrap, width " + (d.width + 6) + "px:null:null, growy"); rotationSlider.addChangeListener(new ChangeListener() { @Override public void stateChanged(ChangeEvent e) { @@ -737,6 +754,20 @@ public class RocketPanel extends JPanel implements TreeSelectionListener, Change } } + private void handleMouseDragged(MouseEvent event, Point originalDragLocation, double originalRotation) { + if (originalDragLocation == null || is3d) { + return; + } + + int dy = event.getY() - originalDragLocation.y; + + double rotationOffset = ((double) dy / scrollPane.getHeight()) * Math.PI; + double newRotation = originalRotation - rotationOffset; + // Ensure the rotation is within the range [0, 2*PI] + newRotation = (newRotation + 2 * Math.PI) % (2 * Math.PI); + figure.setRotation(newRotation); + } + /** * Updates the extra data included in the figure. Currently this includes * the CP and CG carets. Also start the background simulator.