diff --git a/.idea/runConfigurations/Openrocket_UI_Jar.xml b/.idea/runConfigurations/Openrocket_UI_Jar.xml index b00ffdb39..2219ccf09 100644 --- a/.idea/runConfigurations/Openrocket_UI_Jar.xml +++ b/.idea/runConfigurations/Openrocket_UI_Jar.xml @@ -1,10 +1,12 @@ - \ No newline at end of file diff --git a/core/src/net/sf/openrocket/rocketcomponent/AxialStage.java b/core/src/net/sf/openrocket/rocketcomponent/AxialStage.java index a5607615e..4db208072 100644 --- a/core/src/net/sf/openrocket/rocketcomponent/AxialStage.java +++ b/core/src/net/sf/openrocket/rocketcomponent/AxialStage.java @@ -54,25 +54,7 @@ public class AxialStage extends ComponentAssembly implements FlightConfigurableC public void reset( final FlightConfigurationId fcid){ separations.reset(fcid); } - - /** - * {@inheritDoc} - * not strictly accurate, but this should provide an acceptable estimate for total vehicle size - */ - @Override - public Collection getComponentBounds() { - Collection bounds = new ArrayList(8); - Coordinate[] instanceLocations = this.getInstanceLocations(); - double x_min = instanceLocations[0].x; - double x_max = x_min + this.length; - double r_max = 0; - - addBound(bounds, x_min, r_max); - addBound(bounds, x_max, r_max); - - return bounds; - } - + /** * Check whether the given type can be added to this component. A Stage allows * only BodyComponents to be added. diff --git a/core/src/net/sf/openrocket/rocketcomponent/BodyTube.java b/core/src/net/sf/openrocket/rocketcomponent/BodyTube.java index 141146bc6..4428dfac4 100644 --- a/core/src/net/sf/openrocket/rocketcomponent/BodyTube.java +++ b/core/src/net/sf/openrocket/rocketcomponent/BodyTube.java @@ -10,6 +10,7 @@ import net.sf.openrocket.motor.MotorConfiguration; import net.sf.openrocket.motor.MotorConfigurationSet; import net.sf.openrocket.preset.ComponentPreset; import net.sf.openrocket.startup.Application; +import net.sf.openrocket.util.BoundingBox; import net.sf.openrocket.util.BugException; import net.sf.openrocket.util.Coordinate; import net.sf.openrocket.util.MathUtil; @@ -21,7 +22,7 @@ import net.sf.openrocket.util.MathUtil; * @author Sampo Niskanen */ -public class BodyTube extends SymmetricComponent implements MotorMount, Coaxial { +public class BodyTube extends SymmetricComponent implements BoxBounded, MotorMount, Coaxial { private static final Translator trans = Application.getTranslator(); private double outerRadius = 0; @@ -296,52 +297,19 @@ public class BodyTube extends SymmetricComponent implements MotorMount, Coaxial private static double getFilledVolume(double r, double l) { return Math.PI * r * r * l; } - - - /** - * Adds bounding coordinates to the given set. The body tube will fit within the - * convex hull of the points. - * - * Currently the points are simply a rectangular box around the body tube. - */ - @Override - public Collection getComponentBounds() { - Collection bounds = new ArrayList(8); - double x_min_shape = 0; - double x_max_shape = this.length; - double r_max_shape = getOuterRadius(); - - Coordinate[] locs = this.getLocations(); - // not strictly accurate, but this should provide an acceptable estimate for total vehicle size - double x_min_inst = Double.MAX_VALUE; - double x_max_inst = Double.MIN_VALUE; - double r_max_inst = 0.0; - - // refactor: get component inherent bounds - for (Coordinate cur : locs) { - double x_cur = cur.x; - double r_cur = MathUtil.hypot(cur.y, cur.z); - if (x_min_inst > x_cur) { - x_min_inst = x_cur; - } - if (x_max_inst < x_cur) { - x_max_inst = x_cur; - } - if (r_cur > r_max_inst) { - r_max_inst = r_cur; - } - } - - // combine the position bounds with the inherent shape bounds - double x_min = x_min_shape + x_min_inst; - double x_max = x_max_shape + x_max_inst; - double r_max = r_max_shape + r_max_inst; - - addBoundingBox(bounds, x_min, x_max, r_max); - return bounds; + + public BoundingBox getInstanceBoundingBox(){ + BoundingBox instanceBounds = new BoundingBox(); + + instanceBounds.update(new Coordinate(this.getLength(), 0,0)); + + final double r = getOuterRadius(); + instanceBounds.update(new Coordinate(0,r,r)); + instanceBounds.update(new Coordinate(0,-r,-r)); + + return instanceBounds; } - - + /** * Check whether the given type can be added to this component. BodyTubes allow any * InternalComponents or ExternalComponents, excluding BodyComponents, to be added. diff --git a/core/src/net/sf/openrocket/rocketcomponent/BoxBounded.java b/core/src/net/sf/openrocket/rocketcomponent/BoxBounded.java new file mode 100644 index 000000000..700d2c9d4 --- /dev/null +++ b/core/src/net/sf/openrocket/rocketcomponent/BoxBounded.java @@ -0,0 +1,15 @@ +package net.sf.openrocket.rocketcomponent; + +import net.sf.openrocket.util.BoundingBox; + +public interface BoxBounded { + + /** + * Get a bounding box for a single instance of this component, from its own reference point. + * This is expected to be compbined with a InstanceContext for bounds in the global / rocket frame. + * + * @return BoundingBox from the instance's reference point. + */ + BoundingBox getInstanceBoundingBox(); + +} diff --git a/core/src/net/sf/openrocket/rocketcomponent/ComponentAssembly.java b/core/src/net/sf/openrocket/rocketcomponent/ComponentAssembly.java index 24468aeb3..cee911aef 100644 --- a/core/src/net/sf/openrocket/rocketcomponent/ComponentAssembly.java +++ b/core/src/net/sf/openrocket/rocketcomponent/ComponentAssembly.java @@ -4,6 +4,7 @@ import java.util.Collection; import java.util.Collections; import java.util.Iterator; +import net.sf.openrocket.util.BoundingBox; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -22,7 +23,7 @@ import net.sf.openrocket.util.Coordinate; * * @author Sampo Niskanen */ -public abstract class ComponentAssembly extends RocketComponent implements AxialPositionable { +public abstract class ComponentAssembly extends RocketComponent implements AxialPositionable, BoxBounded { private static final Logger log = LoggerFactory.getLogger(ComponentAssembly.class); /** @@ -54,7 +55,7 @@ public abstract class ComponentAssembly extends RocketComponent implements Axia public Collection getComponentBounds() { return Collections.emptyList(); } - + /** * Null method (ComponentAssembly has no mass of itself). */ @@ -70,7 +71,9 @@ public abstract class ComponentAssembly extends RocketComponent implements Axia public double getComponentMass() { return 0; } - + + public BoundingBox getInstanceBoundingBox (){ return new BoundingBox(); } + /** * Null method (ComponentAssembly has no mass of itself). */ diff --git a/core/src/net/sf/openrocket/rocketcomponent/FinSet.java b/core/src/net/sf/openrocket/rocketcomponent/FinSet.java index e276782de..b0a0829b7 100644 --- a/core/src/net/sf/openrocket/rocketcomponent/FinSet.java +++ b/core/src/net/sf/openrocket/rocketcomponent/FinSet.java @@ -25,12 +25,8 @@ import net.sf.openrocket.util.Coordinate; import net.sf.openrocket.util.MathUtil; import net.sf.openrocket.util.Transformation; -public abstract class FinSet extends ExternalComponent implements RingInstanceable, AxialPositionable { +public abstract class FinSet extends ExternalComponent implements AxialPositionable, BoxBounded, RingInstanceable { private static final Translator trans = Application.getTranslator(); - - @SuppressWarnings("unused") - private static final Logger log = LoggerFactory.getLogger(FinSet.class); - /** * Maximum allowed cant of fins. @@ -735,37 +731,15 @@ public abstract class FinSet extends ExternalComponent implements RingInstanceab } - public BoundingBox getBoundingBox() { - BoundingBox singleFinBounds= new BoundingBox().update(getFinPoints()); - final double finLength = singleFinBounds.max.x; - final double finHeight = singleFinBounds.max.y; - - Coordinate[] locations = getInstanceLocations(); - double[] angles = getInstanceAngles(); - BoundingBox finSetBox = new BoundingBox(); - - /* - * The fins themselves will be offset by the location itself to match - * the outside radius of a body tube. The bounds encapsulate the outer - * portion of all the tips of the fins. - * - * The height of each fin along the Y axis can be determined by: - * yHeight = cos(angle) * finHeight - * - * The height (depth?) of each fin along the Z axis can be determined by: - * zHeight = sin(angle) * finHeight - * - * The boundingBox should be that box which is the smallest box where - * a convex hull will contain all Coordinates. - */ - for (int i = 0; i < locations.length; i++) { - double y = Math.cos(angles[i]) * finHeight; - double z = Math.sin(angles[i]) * finHeight; - finSetBox.update(locations[i].add(0, y, z)); - finSetBox.update(locations[i].add(finLength, y, z)); - } - - return finSetBox; + public BoundingBox getInstanceBoundingBox(){ + final BoundingBox singleFinBounds = new BoundingBox(); + + singleFinBounds.update(getFinPoints()); + + singleFinBounds.update(new Coordinate( 0, 0, -this.thickness/2)); + singleFinBounds.update(new Coordinate( 0, 0, this.thickness/2)); + + return singleFinBounds; } /** @@ -776,7 +750,7 @@ public abstract class FinSet extends ExternalComponent implements RingInstanceab */ @Override public Collection getComponentBounds() { - Collection bounds = new ArrayList(8); + Collection bounds = new ArrayList<>(8); // should simply return this component's bounds in this component's body frame. diff --git a/core/src/net/sf/openrocket/rocketcomponent/FlightConfiguration.java b/core/src/net/sf/openrocket/rocketcomponent/FlightConfiguration.java index bd9298e0d..02d109b32 100644 --- a/core/src/net/sf/openrocket/rocketcomponent/FlightConfiguration.java +++ b/core/src/net/sf/openrocket/rocketcomponent/FlightConfiguration.java @@ -550,59 +550,75 @@ public class FlightConfiguration implements FlightConfigurableParameter> entry : map.entrySet()) { RocketComponent component = entry.getKey(); + BoundingBox componentBounds = new BoundingBox(); List contexts = entry.getValue(); - - Collection coordinates = new ArrayList(); - /* FinSets already provide a bounding box, so let's use that. - */ - if (component instanceof FinSet) { - bounds.update(((FinSet) component).getBoundingBox()); + + if( ! component.isAerodynamic()){ + // all non-aerodynamic components should be surrounded by aerodynamic ones continue; - } else { - coordinates.addAll(component.getComponentBounds()); } - BoundingBox componentBox = new BoundingBox(); - List transformedCoords = new ArrayList(); - for (InstanceContext ctxt : contexts) { - /* - * If the instance is not active in the current context, then - * skip the bound calculations. This is mildly confusing since - * getActiveInstances() implies that it will only return the - * instances that are active, but it returns all instances and - * the context indicates if it is active or not. - */ - if (!ctxt.active) { + + // FinSets already provide a bounding box, so let's use that. +// System.err.println(String.format("@[%s]: (#%d)", component.getName(), contexts.size())); + if (component instanceof BoxBounded) { + final BoundingBox instanceBounds = ((BoxBounded) component).getInstanceBoundingBox(); + if(instanceBounds.isEmpty()) { + // this component is probably non-physical (like an assembly) or has invalid bounds. Skip. continue; } - for (Coordinate c : coordinates) { - Coordinate tc = null; - /* These components do not need the transform performed in - * order to provide the proper coordinates for length calculation. - * The transformation will cause the values to be calculated - * incorrectly. This should be fixed in the appropriate places - * not handled as one-offs in here. + + for (InstanceContext context : contexts) { + /* + * If the instance is not active in the current context, then + * skip the bound calculations. This is mildly confusing since + * getActiveInstances() implies that it will only return the + * instances that are active, but it returns all instances and + * the context indicates if it is active or not. */ - if ((component instanceof AxialStage) || (component instanceof BodyTube) || - (component instanceof PodSet)) { - tc = c; - } else { - tc = ctxt.transform.transform(c); + if (!context.active) { + // break out of per-instance loop. + break; + } + + componentBounds.update(instanceBounds.transform(context.transform)); + } + } else { + // Legacy Case: These components do not implement the BoxBounded Interface. + Collection instanceCoordinates = component.getComponentBounds(); + for (InstanceContext context : contexts) { + /* + * If the instance is not active in the current context, then + * skip the bound calculations. This is mildly confusing since + * getActiveInstances() implies that it will only return the + * instances that are active, but it returns all instances and + * the context indicates if it is active or not. + */ + if (!context.active) { + continue; + } + + Collection transformedCoords = new ArrayList<>(instanceCoordinates); + // mutating. Transforms coordinates in place. + context.transform.transform(instanceCoordinates); + + for (Coordinate tc : transformedCoords) { + componentBounds.update(tc); } - componentBox.update(tc); - transformedCoords.add(tc); } } - - bounds.update(componentBox); + +// System.err.println(String.format(" << global: %s", componentBounds.toString())); + rocketBounds.update(componentBounds); +// System.err.println(String.format("<<++ rocket: %s", rocketBounds.toString())); } boundsModID = rocket.getModID(); - cachedLength = bounds.span().x; + cachedLength = rocketBounds.span().x; /* Special case for the scenario that all of the stages are removed and are * inactive. Its possible that this shouldn't be allowed, but it is currently * so we'll just adjust the length here. @@ -610,7 +626,7 @@ public class FlightConfiguration implements FlightConfigurableParameter : ABORTING setPoint(..) !! ", index, xRequest, yRequest)); + points.set(index, revertPoint); return; } @@ -395,7 +393,8 @@ public class FreeformFinSet extends FinSet { */ private boolean intersects(final int targetIndex) { if ((points.size() - 2) < targetIndex) { - throw new IndexOutOfBoundsException("request validate of non-existent fin edge segment: " + targetIndex + "/" + points.size()); + log.error("request validation of non-existent fin edge segment: " + targetIndex + "/" + points.size()); + // throw new IndexOutOfBoundsException("request validate of non-existent fin edge segment: " + targetIndex + "/" + points.size()); } // (pre-check the indices above.) @@ -419,9 +418,9 @@ public class FreeformFinSet extends FinSet { final Line2D.Double comparisonLine = new Line2D.Double(pc1, pc2); if (targetLine.intersectsLine(comparisonLine)) { - log.error(String.format("Found intersection at %d-%d and %d-%d", targetIndex, targetIndex+1, comparisonIndex, comparisonIndex+1)); - log.error(String.format(" between (%g, %g) => (%g, %g)", pt1.x, pt1.y, pt2.x, pt2.y)); - log.error(String.format(" and (%g, %g) => (%g, %g)", pc1.x, pc1.y, pc2.x, pc2.y)); + log.warn(String.format("Found intersection at %d-%d and %d-%d", targetIndex, targetIndex+1, comparisonIndex, comparisonIndex+1)); + log.warn(String.format(" between (%g, %g) => (%g, %g)", pt1.x, pt1.y, pt2.x, pt2.y)); + log.warn(String.format(" and (%g, %g) => (%g, %g)", pc1.x, pc1.y, pc2.x, pc2.y)); return true; } } diff --git a/core/src/net/sf/openrocket/rocketcomponent/PodSet.java b/core/src/net/sf/openrocket/rocketcomponent/PodSet.java index e22fc51aa..3e40d899d 100644 --- a/core/src/net/sf/openrocket/rocketcomponent/PodSet.java +++ b/core/src/net/sf/openrocket/rocketcomponent/PodSet.java @@ -3,11 +3,13 @@ package net.sf.openrocket.rocketcomponent; import java.util.ArrayList; import java.util.Collection; +import jdk.jshell.spi.ExecutionControl; import net.sf.openrocket.l10n.Translator; import net.sf.openrocket.rocketcomponent.position.AngleMethod; import net.sf.openrocket.rocketcomponent.position.AxialMethod; import net.sf.openrocket.rocketcomponent.position.RadiusMethod; import net.sf.openrocket.startup.Application; +import net.sf.openrocket.util.BoundingBox; import net.sf.openrocket.util.BugException; import net.sf.openrocket.util.Coordinate; @@ -43,35 +45,7 @@ public class PodSet extends ComponentAssembly implements RingInstanceable { //// Stage return trans.get("PodSet.PodSet"); } - - - // not strictly accurate, but this should provide an acceptable estimate for total vehicle size - @Override - public Collection getComponentBounds() { - Collection bounds = new ArrayList(8); - double x_min = Double.MAX_VALUE; - double x_max = Double.MIN_VALUE; - double r_max = 0; - - Coordinate[] instanceLocations = this.getComponentLocations(); - - for (Coordinate currentInstanceLocation : instanceLocations) { - if (x_min > (currentInstanceLocation.x)) { - x_min = currentInstanceLocation.x; - } - if (x_max < (currentInstanceLocation.x + this.length)) { - x_max = currentInstanceLocation.x + this.length; - } - if (r_max < (this.getRadiusOffset())) { - r_max = this.getRadiusOffset(); - } - } - addBound(bounds, x_min, r_max); - addBound(bounds, x_max, r_max); - - return bounds; - } - + /** * Check whether the given type can be added to this component. A Stage allows * only BodyComponents to be added. @@ -85,7 +59,6 @@ public class PodSet extends ComponentAssembly implements RingInstanceable { return BodyComponent.class.isAssignableFrom(type); } - @Override public double getInstanceAngleIncrement(){ return angleSeparation; diff --git a/core/src/net/sf/openrocket/rocketcomponent/Rocket.java b/core/src/net/sf/openrocket/rocketcomponent/Rocket.java index 2cba46b8b..8cde7c7ad 100644 --- a/core/src/net/sf/openrocket/rocketcomponent/Rocket.java +++ b/core/src/net/sf/openrocket/rocketcomponent/Rocket.java @@ -8,7 +8,7 @@ import org.slf4j.LoggerFactory; import net.sf.openrocket.l10n.Translator; import net.sf.openrocket.rocketcomponent.position.AxialMethod; import net.sf.openrocket.startup.Application; -import net.sf.openrocket.util.ArrayList; +import net.sf.openrocket.util.BoundingBox; import net.sf.openrocket.util.Coordinate; import net.sf.openrocket.util.MathUtil; import net.sf.openrocket.util.StateChangeListener; @@ -83,7 +83,16 @@ public class Rocket extends ComponentAssembly { configSet = new FlightConfigurableParameterSet<>( defaultConfig ); this.selectedConfiguration = defaultConfig; } - + + /** + * Return a bounding box enveloping the rocket. By definition, the bounding box is a convex hull. + * + * Note: this function gets the bounding box for the entire rocket. + * + * @return Return a bounding box enveloping the rocket + */ + public BoundingBox getBoundingBox (){ return selectedConfiguration.getBoundingBox(); } + public String getDesigner() { checkState(); return designer; diff --git a/core/src/net/sf/openrocket/rocketcomponent/SymmetricComponent.java b/core/src/net/sf/openrocket/rocketcomponent/SymmetricComponent.java index df06fb092..eedbbe6d5 100644 --- a/core/src/net/sf/openrocket/rocketcomponent/SymmetricComponent.java +++ b/core/src/net/sf/openrocket/rocketcomponent/SymmetricComponent.java @@ -9,6 +9,7 @@ import java.util.List; import net.sf.openrocket.preset.ComponentPreset; import net.sf.openrocket.rocketcomponent.PodSet; import net.sf.openrocket.rocketcomponent.position.AxialMethod; +import net.sf.openrocket.util.BoundingBox; import net.sf.openrocket.util.Coordinate; import net.sf.openrocket.util.MathUtil; @@ -20,7 +21,7 @@ import net.sf.openrocket.util.MathUtil; * @author Sampo Niskanen */ -public abstract class SymmetricComponent extends BodyComponent implements RadialParent { +public abstract class SymmetricComponent extends BodyComponent implements BoxBounded, RadialParent { public static final double DEFAULT_RADIUS = 0.025; public static final double DEFAULT_THICKNESS = 0.002; @@ -40,13 +41,23 @@ public abstract class SymmetricComponent extends BodyComponent implements Radial private double rotationalInertia = -1; private Coordinate cg = null; - public SymmetricComponent() { super(); } - - + + public BoundingBox getInstanceBoundingBox(){ + BoundingBox instanceBounds = new BoundingBox(); + + instanceBounds.update(new Coordinate(this.getLength(), 0,0)); + + final double r = Math.max(getForeRadius(), getAftRadius()); + instanceBounds.update(new Coordinate(0,r,r)); + instanceBounds.update(new Coordinate(0,-r,-r)); + + return instanceBounds; + } + /** * Return the component radius at position x. * @param x Position on x-axis. diff --git a/core/src/net/sf/openrocket/util/BoundingBox.java b/core/src/net/sf/openrocket/util/BoundingBox.java index 6d5f46eb8..a1f766837 100644 --- a/core/src/net/sf/openrocket/util/BoundingBox.java +++ b/core/src/net/sf/openrocket/util/BoundingBox.java @@ -7,7 +7,7 @@ import java.util.Collection; public class BoundingBox { public Coordinate min; public Coordinate max; - + public BoundingBox() { clear(); } @@ -27,8 +27,36 @@ public class BoundingBox { public BoundingBox clone() { return new BoundingBox( this.min, this.max ); } - - + + public boolean isEmpty(){ + if( (min.x > max.x) || + (min.y > max.y) || + (min.z > max.z)){ + return true; + }else{ + return false; + } + } + + /** + * Generate a new bounding box, transformed by the given transformation matrix + * + * Note: This implementation is not _exact_! Do not use this for Aerodynamic, Mass or Simulation calculations.... + * But it will be sufficiently close for UI purposes. + * + * @return a new box, transform by the given transform + */ + public BoundingBox transform(Transformation transform){ + final Coordinate p1 = transform.transform(this.min); + final Coordinate p2 = transform.transform(this.max); + + final BoundingBox newBox = new BoundingBox(); + newBox.update(p1); + newBox.update(p2); + + return newBox; + } + private void update_x_min( final double xVal) { if( min.x > xVal) min = min.setX( xVal ); @@ -69,16 +97,17 @@ public class BoundingBox { update_z_max(val); return this; } - - public void update( Coordinate c ) { + public BoundingBox update( Coordinate c ) { update_x_min(c.x); update_y_min(c.y); update_z_min(c.z); - + update_x_max(c.x); update_y_max(c.y); update_z_max(c.z); + + return this; } public BoundingBox update( Rectangle2D rect ) { @@ -104,9 +133,12 @@ public class BoundingBox { } public BoundingBox update( BoundingBox other ) { + if(other.isEmpty()){ + return this; + } update_x_min(other.min.x); update_y_min(other.min.y); - update_z_min(other.min.y); + update_z_min(other.min.z); update_x_max(other.max.x); update_y_max(other.max.y); @@ -121,7 +153,7 @@ public class BoundingBox { } public Collection toCollection(){ - Collection toReturn = new ArrayList(); + Collection toReturn = new ArrayList<>(); toReturn.add( this.max); toReturn.add( this.min); return toReturn; diff --git a/core/src/net/sf/openrocket/util/Coordinate.java b/core/src/net/sf/openrocket/util/Coordinate.java index b4d93a76a..c6a9f247a 100644 --- a/core/src/net/sf/openrocket/util/Coordinate.java +++ b/core/src/net/sf/openrocket/util/Coordinate.java @@ -62,8 +62,8 @@ public final class Coordinate implements Cloneable, Serializable { public static final Coordinate ZERO = new Coordinate(0, 0, 0, 0); public static final Coordinate NUL = new Coordinate(0, 0, 0, 0); public static final Coordinate NaN = new Coordinate(Double.NaN, Double.NaN,Double.NaN, Double.NaN); - public static final Coordinate MAX = new Coordinate(Double.MAX_VALUE,Double.MAX_VALUE,Double.MAX_VALUE,Double.MAX_VALUE); - public static final Coordinate MIN = new Coordinate(Double.MIN_VALUE,Double.MIN_VALUE,Double.MIN_VALUE,Double.MIN_VALUE); + public static final Coordinate MAX = new Coordinate( Double.MAX_VALUE, Double.MAX_VALUE, Double.MAX_VALUE,Double.MAX_VALUE); + public static final Coordinate MIN = new Coordinate(-Double.MAX_VALUE,-Double.MAX_VALUE,-Double.MAX_VALUE,0.0); public static final Coordinate X_UNIT = new Coordinate(1, 0, 0); public static final Coordinate Y_UNIT = new Coordinate(0, 1, 0); diff --git a/core/src/net/sf/openrocket/util/TestRockets.java b/core/src/net/sf/openrocket/util/TestRockets.java index f03845045..8636480be 100644 --- a/core/src/net/sf/openrocket/util/TestRockets.java +++ b/core/src/net/sf/openrocket/util/TestRockets.java @@ -1038,12 +1038,11 @@ public class TestRockets { boosterBody.addChild(boosterFins); boosterFins.setName("Booster Fins"); boosterFins.setFinCount(3); - boosterFins.setAngleOffset( Math.PI / 4); boosterFins.setThickness(0.003); boosterFins.setCrossSection(CrossSection.ROUNDED); boosterFins.setRootChord(0.32); boosterFins.setTipChord(0.12); - boosterFins.setHeight(0.12); + boosterFins.setHeight(0.10); boosterFins.setSweep(0.18); boosterFins.setAxialMethod(AxialMethod.BOTTOM); boosterFins.setAxialOffset(0.0); diff --git a/core/test/net/sf/openrocket/aerodynamics/BarrowmanCalculatorTest.java b/core/test/net/sf/openrocket/aerodynamics/BarrowmanCalculatorTest.java index 1ac76f235..9f285a709 100644 --- a/core/test/net/sf/openrocket/aerodynamics/BarrowmanCalculatorTest.java +++ b/core/test/net/sf/openrocket/aerodynamics/BarrowmanCalculatorTest.java @@ -123,22 +123,23 @@ public class BarrowmanCalculatorTest { { boosterFins.setFinCount(3); final Coordinate cp_3fin = calc.getCP(config, conditions, warnings); - assertEquals(" Falcon 9 Heavy CNa value is incorrect:", 21.5111598, cp_3fin.weight, EPSILON); - assertEquals(" Falcon 9 Heavy CP x value is incorrect:", 1.04662388, cp_3fin.x, EPSILON); + assertEquals(" Falcon 9 Heavy CNa value is incorrect:", 16.51651439, cp_3fin.weight, EPSILON); + assertEquals(" Falcon 9 Heavy CP x value is incorrect:", 1.00667319, cp_3fin.x, EPSILON); assertEquals(" Falcon 9 Heavy CP y value is incorrect:", 0.0, cp_3fin.y, EPSILON); assertEquals(" Falcon 9 Heavy CP z value is incorrect:", 0.0, cp_3fin.z, EPSILON); }{ boosterFins.setFinCount(2); + boosterFins.setAngleOffset(Math.PI/4); final Coordinate cp_2fin = calc.getCP(config, conditions, warnings); - assertEquals(" Falcon 9 Heavy CNa value is incorrect:", 15.43711196967902, cp_2fin.weight, EPSILON); - assertEquals(" Falcon 9 Heavy CP x value is incorrect:", 0.9946423753010524, cp_2fin.x, EPSILON); + assertEquals(" Falcon 9 Heavy CNa value is incorrect:", 12.1073483560, cp_2fin.weight, EPSILON); + assertEquals(" Falcon 9 Heavy CP x value is incorrect:", 0.9440139181, cp_2fin.x, EPSILON); assertEquals(" Falcon 9 Heavy CP y value is incorrect:", 0.0, cp_2fin.y, EPSILON); assertEquals(" Falcon 9 Heavy CP z value is incorrect:", 0.0, cp_2fin.z, EPSILON); }{ boosterFins.setFinCount(1); final Coordinate cp_1fin = calc.getCP(config, conditions, warnings); - assertEquals(" Falcon 9 Heavy CNa value is incorrect:", 9.36306412, cp_1fin.weight, EPSILON); - assertEquals(" Falcon 9 Heavy CP x value is incorrect:", 0.87521867, cp_1fin.x, EPSILON); + assertEquals(" Falcon 9 Heavy CNa value is incorrect:", 7.6981823141, cp_1fin.weight, EPSILON); + assertEquals(" Falcon 9 Heavy CP x value is incorrect:", 0.8095779106, cp_1fin.x, EPSILON); assertEquals(" Falcon 9 Heavy CP y value is incorrect:", 0f, cp_1fin.y, EPSILON); assertEquals(" Falcon 9 Heavy CP z value is incorrect:", 0f, cp_1fin.z, EPSILON); } diff --git a/core/test/net/sf/openrocket/masscalc/MassCalculatorTest.java b/core/test/net/sf/openrocket/masscalc/MassCalculatorTest.java index d6e76cb18..07875f0e1 100644 --- a/core/test/net/sf/openrocket/masscalc/MassCalculatorTest.java +++ b/core/test/net/sf/openrocket/masscalc/MassCalculatorTest.java @@ -240,7 +240,7 @@ public class MassCalculatorTest extends BaseTestCase { compMass = mmt.getComponentMass(); assertEquals(mmt.getName() + " mass calculated incorrectly: ", expMass, compMass, EPSILON); - expMass = 0.15995232; + expMass = 0.13329359999999998; final FinSet boosterFins = (FinSet) boosters.getChild(1).getChild(1); compMass = boosterFins.getComponentMass(); assertEquals(boosterFins.getName() + " mass calculated incorrectly: ", expMass, compMass, EPSILON); @@ -442,10 +442,10 @@ public class MassCalculatorTest extends BaseTestCase { assertEquals(cc.getName() + " Longitudinal MOI calculated incorrectly: ", expInertia, compInertia, EPSILON); final FinSet boosterFins = (FinSet) boosters.getChild(1).getChild(1); - expInertia = 0.001377661595723823; + expInertia = 0.000928545614574877; compInertia = boosterFins.getRotationalInertia(); assertEquals(boosterFins.getName() + " Rotational MOI calculated incorrectly: ", expInertia, compInertia, EPSILON); - expInertia = 0.0016272177418619116; + expInertia = 0.001246261927287438; compInertia = boosterFins.getLongitudinalInertia(); assertEquals(boosterFins.getName() + " Longitudinal MOI calculated incorrectly: ", expInertia, compInertia, EPSILON); @@ -558,8 +558,8 @@ public class MassCalculatorTest extends BaseTestCase { final RigidBody actualData = MassCalculator.calculateStructure(config); final Coordinate actualCM = actualData.getCM(); - double expMass = 0.66198084; - double expCMx = 1.08642949; + double expMass = 0.608663395; + double expCMx = 1.073157592; assertEquals("Heavy Booster Mass is incorrect: ", expMass, actualCM.weight, EPSILON); assertEquals("Heavy Booster CM.x is incorrect: ", expCMx, actualCM.x, EPSILON); @@ -578,11 +578,11 @@ public class MassCalculatorTest extends BaseTestCase { RigidBody actualBoosterLaunchData = MassCalculator.calculateLaunch(config); double actualMass = actualBoosterLaunchData.getMass(); - double expectedMass = 1.64598084; + double expectedMass = 1.592663395; assertEquals(" Booster Launch Mass is incorrect: ", expectedMass, actualMass, EPSILON); final Coordinate actualCM = actualBoosterLaunchData.getCM(); - double expectedCMx = 1.22267891; + double expectedCMx = 1.22216804; Coordinate expCM = new Coordinate(expectedCMx, 0, 0, expectedMass); assertEquals(" Booster Launch CM.x is incorrect: ", expCM.x, actualCM.x, EPSILON); assertEquals(" Booster Launch CM.y is incorrect: ", expCM.y, actualCM.y, EPSILON); @@ -602,8 +602,8 @@ public class MassCalculatorTest extends BaseTestCase { RigidBody spentData = MassCalculator.calculateBurnout(config); Coordinate spentCM = spentData.getCM(); - double expSpentMass = 1.17398084; - double expSpentCMx = 1.18582650; + double expSpentMass = 1.12066339; + double expSpentCMx = 1.18334714; Coordinate expLaunchCM = new Coordinate(expSpentCMx, 0, 0, expSpentMass); assertEquals(" Booster Launch Mass is incorrect: ", expLaunchCM.weight, spentCM.weight, EPSILON); assertEquals(" Booster Launch CM.x is incorrect: ", expLaunchCM.x, spentCM.x, EPSILON); @@ -668,11 +668,11 @@ public class MassCalculatorTest extends BaseTestCase { RigidBody spent = MassCalculator.calculateBurnout(config); - double expMOIRotational = 0.010420016485489425; + double expMOIRotational = 0.009205665421431532; double boosterMOIRotational = spent.getRotationalInertia(); assertEquals(" Booster x-axis MOI is incorrect: ", expMOIRotational, boosterMOIRotational, EPSILON); - double expMOI_tr = 0.05913869705973017; + double expMOI_tr = 0.0582250994240395; double boosterMOI_tr = spent.getLongitudinalInertia(); assertEquals(" Booster transverse MOI is incorrect: ", expMOI_tr, boosterMOI_tr, EPSILON); } @@ -688,9 +688,9 @@ public class MassCalculatorTest extends BaseTestCase { RigidBody launchData = MassCalculator.calculateLaunch(config); - final double expIxx = 0.013480523485489424; + final double expIxx = 0.01226617242143153; final double actIxx = launchData.getRotationalInertia(); - final double expIyy = 0.06532830810235105; + final double expIyy = 0.06455356411879717; final double actIyy = launchData.getLongitudinalInertia(); assertEquals(" Booster x-axis MOI is incorrect: ", expIxx, actIxx, EPSILON); @@ -731,11 +731,11 @@ public class MassCalculatorTest extends BaseTestCase { assertEquals(" Booster Launch CM is incorrect: ", expCM, boosterSetCM); // Validate MOI - double expMOI_axial = 0.007100144485489424; + double expMOI_axial = 0.005885793421431532; double boosterMOI_xx = burnout.getRotationalInertia(); assertEquals(" Booster x-axis MOI is incorrect: ", expMOI_axial, boosterMOI_xx, EPSILON); - double expMOI_tr = 16.025778716205167; + double expMOI_tr = 14.815925423036177; double boosterMOI_tr = burnout.getLongitudinalInertia(); assertEquals(" Booster transverse MOI is incorrect: ", expMOI_tr, boosterMOI_tr, EPSILON); } @@ -774,10 +774,10 @@ public class MassCalculatorTest extends BaseTestCase { Coordinate boosterCM = boosterData.getCM(); // cm= 3.409905g@[0.853614,-0.000000,0.000000] - double expTotalMass = 3.40990464; + double expTotalMass = 3.3565872; assertEquals(" Booster Launch Mass is incorrect: ", expTotalMass, boosterData.getMass(), EPSILON); - double expCMx = 0.85361377; + double expCMx = 0.847508988; Coordinate expCM = new Coordinate(expCMx, 0, 0, expTotalMass); assertEquals(" Booster Launch CM.x is incorrect: ", expCM.x, boosterCM.x, EPSILON); assertEquals(" Booster Launch CM.y is incorrect: ", expCM.y, boosterCM.y, EPSILON); @@ -785,11 +785,11 @@ public class MassCalculatorTest extends BaseTestCase { assertEquals(" Booster Launch CM is incorrect: ", expCM, boosterCM); // Validate MOI - double expMOI_axial = 0.026027963480146098; + double expMOI_axial = 0.024813612416088204; double boosterMOI_xx = boosterData.getRotationalInertia(); assertEquals(" Booster x-axis MOI is incorrect: ", expMOI_axial, boosterMOI_xx, EPSILON); - double expMOI_tr = 0.35444021118310487; + double expMOI_tr = 0.34567788938578525; double boosterMOI_tr = boosterData.getLongitudinalInertia(); assertEquals(" Booster transverse MOI is incorrect: ", expMOI_tr, boosterMOI_tr, EPSILON); } @@ -820,11 +820,11 @@ public class MassCalculatorTest extends BaseTestCase { RigidBody structure = MassCalculator.calculateStructure(config); - final double expMass = 0.66198084; + final double expMass = 0.6086633952494; double calcTotalMass = structure.getMass(); assertEquals(" Booster Launch Mass is incorrect: ", expMass, calcTotalMass, EPSILON); - final double expCMx = 1.12869951; + final double expCMx = 1.1191303646; Coordinate expCM = new Coordinate(expCMx, 0, 0, expMass); assertEquals(" Booster Launch CM.x is incorrect: ", expCM.x, structure.getCM().x, EPSILON); assertEquals(" Booster Launch CM.y is incorrect: ", expCM.y, structure.getCM().y, EPSILON); @@ -832,11 +832,11 @@ public class MassCalculatorTest extends BaseTestCase { assertEquals(" Booster Launch CM is incorrect: ", expCM, structure.getCM()); // Validate MOI - final double expMOI_axial = 0.007100144485489424; + final double expMOI_axial = 0.005885793421431532; double boosterMOI_xx = structure.getRotationalInertia(); assertEquals(" Booster x-axis MOI is incorrect: ", expMOI_axial, boosterMOI_xx, EPSILON); - final double expMOI_tr = 0.04244299775219597; + final double expMOI_tr = 0.04098909591063; double boosterMOI_tr = structure.getLongitudinalInertia(); assertEquals(" Booster transverse MOI is incorrect: ", expMOI_tr, boosterMOI_tr, EPSILON); } @@ -886,8 +886,8 @@ public class MassCalculatorTest extends BaseTestCase { // ======================================================================= - // DEBUG - System.err.println(rocket.toDebugTree()); + // // DEBUG + // System.err.println(rocket.toDebugTree()); RigidBody structure = MassCalculator.calculateStructure(config); final double expMass = 0.0900260149; diff --git a/core/test/net/sf/openrocket/rocketcomponent/FlightConfigurationTest.java b/core/test/net/sf/openrocket/rocketcomponent/FlightConfigurationTest.java index de5eb006f..ae7002db3 100644 --- a/core/test/net/sf/openrocket/rocketcomponent/FlightConfigurationTest.java +++ b/core/test/net/sf/openrocket/rocketcomponent/FlightConfigurationTest.java @@ -463,25 +463,24 @@ public class FlightConfigurationTest extends BaseTestCase { assertThat(boosterFinContext0.instanceNumber, equalTo(0)); final Coordinate boosterFin0Location = boosterFinContext0.getLocation(); assertEquals(1.044, boosterFin0Location.x, EPSILON); - assertEquals( -0.104223611, boosterFin0Location.y, EPSILON); - assertEquals( -0.027223611, boosterFin0Location.z, EPSILON); + assertEquals( -0.1155, boosterFin0Location.y, EPSILON); + assertEquals( 0.0, boosterFin0Location.z, EPSILON); final InstanceContext boosterFinContext1 = finContextList.get(4); assertThat((Class) boosterFinContext1.component.getClass(), equalTo(TrapezoidFinSet.class)); assertThat(boosterFinContext1.instanceNumber, equalTo(1)); final Coordinate boosterFin1Location = boosterFinContext1.getLocation(); - assertEquals(boosterFin1Location.x, 1.044, EPSILON); - assertEquals(boosterFin1Location.y, -0.03981186, EPSILON); - assertEquals(boosterFin1Location.z, -0.00996453, EPSILON); + assertEquals( 1.044, boosterFin1Location.x, EPSILON); + assertEquals(-0.05775, boosterFin1Location.y, EPSILON); + assertEquals(-0.033341978, boosterFin1Location.z, EPSILON); final InstanceContext boosterFinContext2 = finContextList.get(5); assertThat((Class) boosterFinContext2.component.getClass(), equalTo(TrapezoidFinSet.class)); assertThat(boosterFinContext2.instanceNumber, equalTo(2)); final Coordinate boosterFin2Location = boosterFinContext2.getLocation(); - assertEquals(boosterFin2Location.x, 1.044, EPSILON); - assertEquals(boosterFin2Location.y, -0.08696453, EPSILON); - assertEquals(boosterFin2Location.z, 0.03718814, EPSILON); - + assertEquals(1.044, boosterFin2Location.x, EPSILON); + assertEquals(-0.05775, boosterFin2Location.y, EPSILON); + assertEquals( 0.03334, boosterFin2Location.z, EPSILON); } } diff --git a/core/test/net/sf/openrocket/rocketcomponent/RocketTest.java b/core/test/net/sf/openrocket/rocketcomponent/RocketTest.java index 484ad05ac..dfe779c7d 100644 --- a/core/test/net/sf/openrocket/rocketcomponent/RocketTest.java +++ b/core/test/net/sf/openrocket/rocketcomponent/RocketTest.java @@ -6,6 +6,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThat; import net.sf.openrocket.rocketcomponent.position.AxialMethod; +import net.sf.openrocket.util.BoundingBox; import org.junit.Test; import net.sf.openrocket.rocketcomponent.position.AngleMethod; @@ -58,10 +59,10 @@ public class RocketTest extends BaseTestCase { @Test public void testEstesAlphaIII(){ - Rocket rocket = TestRockets.makeEstesAlphaIII(); - - AxialStage stage= (AxialStage)rocket.getChild(0); - + final Rocket rocket = TestRockets.makeEstesAlphaIII(); + + final AxialStage stage= (AxialStage)rocket.getChild(0); + Coordinate expLoc; Coordinate actLoc; { @@ -136,6 +137,15 @@ public class RocketTest extends BaseTestCase { } } + + final BoundingBox bounds = rocket.getBoundingBox(); + assertEquals(bounds.min.x, 0.0, EPSILON); + assertEquals(bounds.max.x, 0.27, EPSILON); + + assertEquals( -0.032385640, bounds.min.y, EPSILON); + assertEquals( -0.054493575, bounds.min.z, EPSILON); + assertEquals( 0.062000000, bounds.max.y, EPSILON); + assertEquals( 0.052893575, bounds.max.z, EPSILON); } @Test @@ -270,6 +280,15 @@ public class RocketTest extends BaseTestCase { assertThat(mmt.getName()+" not positioned correctly: ", actLoc, equalTo( expLoc )); } } + + final BoundingBox bounds = rocket.getBoundingBox(); + assertEquals( bounds.min.x, 0.0, EPSILON); + assertEquals( bounds.max.x, 0.335, EPSILON); + + assertEquals( -0.032385640, bounds.min.y, EPSILON); + assertEquals( -0.054493575, bounds.min.z, EPSILON); + assertEquals( 0.062000000, bounds.max.y, EPSILON); + assertEquals( 0.052893575, bounds.max.z, EPSILON); } @Test @@ -389,8 +408,20 @@ public class RocketTest extends BaseTestCase { assertEquals(coreFins.getName()+" location is incorrect: ", 1.044, loc.x, EPSILON); } } - } + + // DEBUG + System.err.println(rocket.toDebugTree()); + + final BoundingBox bounds = rocket.getBoundingBox(); + assertEquals( 0.0, bounds.min.x, EPSILON); + assertEquals( 1.364, bounds.max.x, EPSILON); + + assertEquals( -0.215500, bounds.min.y, EPSILON); + assertEquals( 0.215500, bounds.max.y, EPSILON); + + assertEquals( -0.12069451, bounds.min.z, EPSILON); + assertEquals( 0.12069451, bounds.max.z, EPSILON); } } diff --git a/swing/src/net/sf/openrocket/gui/figure3d/geometry/FinRenderer.java b/swing/src/net/sf/openrocket/gui/figure3d/geometry/FinRenderer.java index 3dca72e65..ddd3acf66 100644 --- a/swing/src/net/sf/openrocket/gui/figure3d/geometry/FinRenderer.java +++ b/swing/src/net/sf/openrocket/gui/figure3d/geometry/FinRenderer.java @@ -19,7 +19,7 @@ public class FinRenderer { public void renderFinSet(final GL2 gl, FinSet finSet ) { - BoundingBox bounds = finSet.getBoundingBox(); + BoundingBox bounds = finSet.getInstanceBoundingBox(); gl.glMatrixMode(GL.GL_TEXTURE); gl.glPushMatrix(); gl.glScaled(1 / (bounds.max.x - bounds.min.x), 1 / (bounds.max.y - bounds.min.y), 0); diff --git a/swing/src/net/sf/openrocket/gui/print/PrintFigure.java b/swing/src/net/sf/openrocket/gui/print/PrintFigure.java index 0027e7a89..945ed2d7e 100644 --- a/swing/src/net/sf/openrocket/gui/print/PrintFigure.java +++ b/swing/src/net/sf/openrocket/gui/print/PrintFigure.java @@ -30,14 +30,14 @@ public class PrintFigure extends RocketFigure { } public double getFigureHeight() { - return this.subjectBounds_m.getHeight(); + return this.contentBounds_m.getHeight(); } public double getFigureWidth() { - return this.subjectBounds_m.getWidth(); + return this.contentBounds_m.getWidth(); } public Rectangle2D getDimensions() { - return this.subjectBounds_m.getBounds2D(); + return this.contentBounds_m.getBounds2D(); } } diff --git a/swing/src/net/sf/openrocket/gui/scalefigure/AbstractScaleFigure.java b/swing/src/net/sf/openrocket/gui/scalefigure/AbstractScaleFigure.java index eba695c4d..60575caf5 100644 --- a/swing/src/net/sf/openrocket/gui/scalefigure/AbstractScaleFigure.java +++ b/swing/src/net/sf/openrocket/gui/scalefigure/AbstractScaleFigure.java @@ -4,6 +4,7 @@ import java.awt.Color; import java.awt.Dimension; import java.awt.geom.AffineTransform; import java.awt.geom.Rectangle2D; +import java.awt.Point; import java.util.EventListener; import java.util.EventObject; import java.util.LinkedList; @@ -32,43 +33,46 @@ public abstract class AbstractScaleFigure extends JPanel { // Number of pixels to leave at edges when fitting figure private static final int DEFAULT_BORDER_PIXELS_WIDTH = 30; private static final int DEFAULT_BORDER_PIXELS_HEIGHT = 20; - - // constant factor that scales screen real-estate to rocket-space - private final double baseScale; - private double userScale = 1.0; - protected double scale = -1; - protected static final Dimension borderThickness_px = new Dimension(DEFAULT_BORDER_PIXELS_WIDTH, DEFAULT_BORDER_PIXELS_HEIGHT); - // pixel offset from the the subject's origin to the canvas's upper-left-corner. - protected Dimension originLocation_px = new Dimension(0,0); + + // constant factor that scales screen real-estate to rocket-space + protected final double baseScale; + protected double userScale = 1.0; + protected double scale = -1; + + // pixel offset from the the subject's origin to the canvas's upper-left-corner. + protected Point originLocation_px = new Point(0,0); // size of the visible region protected Dimension visibleBounds_px = new Dimension(0,0); - // ======= whatever this figure is drawing, in real-space coordinates: meters - protected Rectangle2D subjectBounds_m = null; - - // combines the translation and scale in one place: - // which frames does this transform between ? - protected AffineTransform projection = null; + // ======= whatever this figure is drawing, in real-space coordinates: meters + // all drawable content + protected Rectangle2D contentBounds_m = new Rectangle2D.Double(0,0,0,0); + // the content we should focus on (this is the auto-zoom subject) + protected Rectangle2D subjectBounds_m = new Rectangle2D.Double(0,0,0,0); + + // combines the translation and scale in one place: + // which frames does this transform between ? + protected AffineTransform projection = null; protected final List listeners = new LinkedList(); public AbstractScaleFigure() { - // produces a pixels-per-meter scale factor - // - // dots dots inch - // ---- = ------ * ----- - // meter inch meter - // - this.baseScale = GUIUtil.getDPI() * INCHES_PER_METER; - this.userScale = 1.0; - this.scale = baseScale * userScale; - - this.setPreferredSize(new Dimension(100,100)); - setSize(100,100); - + // produces a pixels-per-meter scale factor + // + // dots dots inch + // ---- = ------ * ----- + // meter inch meter + // + this.baseScale = GUIUtil.getDPI() * INCHES_PER_METER; + this.userScale = 1.0; + this.scale = baseScale * userScale; + + this.setPreferredSize(new Dimension(100,100)); + setSize(100,100); + setBackground(Color.WHITE); setOpaque(true); } @@ -77,29 +81,43 @@ public abstract class AbstractScaleFigure extends JPanel { return userScale; } - public double getAbsoluteScale() { - return scale; - } + public double getAbsoluteScale() { + return scale; + } + + public Point getSubjectOrigin() { + return originLocation_px; + } + + /** + * Calculate a point for auto-zooming from a scale-to-fit request. + * + * The return point is intended for a $ScaleScrollPane call to "viewport.scrollRectToVisible(...)" + * + * @return the offset, in pixels, from the (top left) corner of the figure's canvas + */ + public abstract Point getAutoZoomPoint(); - public Dimension getSubjectOrigin() { - return originLocation_px; - } - /** * Set the scale level of the figure. A scale value of 1.0 is equivalent to 100 % scale. * Smaller scales display the subject smaller. * * If the figure would be smaller than the 'visibleBounds', then the figure is grown to match, * and the figures internal contents are centered according to the figure's origin. - * + * * @param newScaleRequest the scale level * @param visibleBounds the visible bounds upon the Figure */ public void scaleTo(final double newScaleRequest, final Dimension visibleBounds) { - if (MathUtil.equals(this.userScale, newScaleRequest, 0.01)){ +// System.err.println(String.format(" ::scaleTo: %6.4f ==>> %6.4f, %d x %d", userScale, newScaleRequest, visibleBounds.width, visibleBounds.height)); + if (MathUtil.equals(this.userScale, newScaleRequest, 0.01) && + (visibleBounds_px.width == visibleBounds.width) && + (visibleBounds_px.height == visibleBounds.height) ) + { return;} - if (Double.isInfinite(newScaleRequest) || Double.isNaN(newScaleRequest)) { + if (Double.isInfinite(newScaleRequest) || Double.isNaN(newScaleRequest) || 0 > newScaleRequest) { return;} +// System.err.println(String.format(" => continue")); this.userScale = MathUtil.clamp( newScaleRequest, MINIMUM_ZOOM, MAXIMUM_ZOOM); this.scale = baseScale * userScale; @@ -115,22 +133,19 @@ public abstract class AbstractScaleFigure extends JPanel { * @param visibleBounds the visible bounds to scale this figure to. */ public void scaleTo(Dimension visibleBounds) { - if( 0 == visibleBounds.getWidth() || 0 == visibleBounds.getHeight()) - return; - - updateSubjectDimensions(); - - // dimensions within the viewable area, which are available to draw - final int drawable_width_px = visibleBounds.width - 2 * borderThickness_px.width; - final int drawable_height_px = visibleBounds.height - 2 * borderThickness_px.height; +// System.err.println(String.format(" ::scaleTo: %d x %d", visibleBounds.width, visibleBounds.height)); + if( 0 >= visibleBounds.getWidth() || 0 >= visibleBounds.getHeight()) + return; - if(( 0 < drawable_width_px ) && ( 0 < drawable_height_px)) { - final double width_scale = (drawable_width_px) / ( subjectBounds_m.getWidth() * baseScale); - final double height_scale = (drawable_height_px) / ( subjectBounds_m.getHeight() * baseScale); - final double minScale = Math.min(height_scale, width_scale); - - scaleTo(minScale, visibleBounds); - } + updateSubjectDimensions(); + updateCanvasSize(); + updateCanvasOrigin(); + + final double width_scale = (visibleBounds.width) / ((subjectBounds_m.getWidth() * baseScale) + 2 * borderThickness_px.width); + final double height_scale = (visibleBounds.height) / ((subjectBounds_m.getHeight() * baseScale) + 2 * borderThickness_px.height); + final double newScale = Math.min(height_scale, width_scale); + + scaleTo(newScale, visibleBounds); } /** @@ -148,9 +163,9 @@ public abstract class AbstractScaleFigure extends JPanel { */ protected void updateCanvasSize() { final int desiredWidth = Math.max((int)this.visibleBounds_px.getWidth(), - (int)(subjectBounds_m.getWidth()*scale) + 2*borderThickness_px.width); + (int)(contentBounds_m.getWidth()*scale) + 2*borderThickness_px.width); final int desiredHeight = Math.max((int)this.visibleBounds_px.getHeight(), - (int)(subjectBounds_m.getHeight()*scale) + 2*borderThickness_px.height); + (int)(contentBounds_m.getHeight()*scale) + 2*borderThickness_px.height); Dimension preferredFigureSize_px = new Dimension(desiredWidth, desiredHeight); @@ -162,7 +177,7 @@ public abstract class AbstractScaleFigure extends JPanel { // Calculate and store the transformation used // (inverse is used in detecting clicks on objects) projection = new AffineTransform(); - projection.translate(this.originLocation_px.width, originLocation_px.height); + projection.translate(this.originLocation_px.x, originLocation_px.y); // Mirror position Y-axis upwards projection.scale(scale, -scale); } @@ -171,7 +186,7 @@ public abstract class AbstractScaleFigure extends JPanel { * Updates the figure shapes and figure size. */ public void updateFigure() { - log.debug(String.format("____ Updating %s to: %g user scale, %g overall scale", this.getClass().getSimpleName(), this.getAbsoluteScale(), this.scale)); + log.trace(String.format("____ Updating %s to: %g user scale, %g overall scale", this.getClass().getSimpleName(), this.getAbsoluteScale(), this.scale)); updateSubjectDimensions(); updateCanvasSize(); @@ -181,10 +196,6 @@ public abstract class AbstractScaleFigure extends JPanel { revalidate(); repaint(); } - - protected Dimension getBorderPixels() { - return borderThickness_px; - } public void addChangeListener(StateChangeListener listener) { listeners.add(0, listener); diff --git a/swing/src/net/sf/openrocket/gui/scalefigure/FinPointFigure.java b/swing/src/net/sf/openrocket/gui/scalefigure/FinPointFigure.java index 1247b8b62..d2d6fda78 100644 --- a/swing/src/net/sf/openrocket/gui/scalefigure/FinPointFigure.java +++ b/swing/src/net/sf/openrocket/gui/scalefigure/FinPointFigure.java @@ -2,9 +2,9 @@ package net.sf.openrocket.gui.scalefigure; import java.awt.BasicStroke; import java.awt.Color; -import java.awt.Dimension; import java.awt.Graphics; import java.awt.Graphics2D; +import java.awt.Point; import java.awt.Rectangle; import java.awt.RenderingHints; import java.awt.geom.Line2D; @@ -15,7 +15,6 @@ import java.awt.geom.Rectangle2D; import java.util.LinkedList; import java.util.List; -import org.slf4j.*; import net.sf.openrocket.rocketcomponent.FreeformFinSet; import net.sf.openrocket.rocketcomponent.RocketComponent; import net.sf.openrocket.rocketcomponent.SymmetricComponent; @@ -33,34 +32,34 @@ import net.sf.openrocket.util.StateChangeListener; @SuppressWarnings("serial") public class FinPointFigure extends AbstractScaleFigure { - //private final static Logger log = LoggerFactory.getLogger(FinPointFigure.class); + //private final static Logger log = LoggerFactory.getLogger(FinPointFigure.class); - private static final Color GRID_MAJOR_LINE_COLOR = new Color( 64, 64, 128, 128); - private static final Color GRID_MINOR_LINE_COLOR = new Color( 64, 64, 128, 32); - private static final int GRID_LINE_BASE_WIDTH_PIXELS = 1; + private static final Color GRID_MAJOR_LINE_COLOR = new Color( 64, 64, 128, 128); + private static final Color GRID_MINOR_LINE_COLOR = new Color( 64, 64, 128, 32); + private static final int GRID_LINE_BASE_WIDTH_PIXELS = 1; - private static final int LINE_WIDTH_FIN_PIXELS = 1; - private static final int LINE_WIDTH_BODY_PIXELS = 2; - - // the size of the boxes around each fin point vertex - private static final int LINE_WIDTH_BOX_PIXELS = 1; - private static final float BOX_WIDTH_PIXELS = 12; - private static final float SELECTED_BOX_WIDTH_PIXELS = BOX_WIDTH_PIXELS + 4; - private static final Color POINT_COLOR = new Color(100, 100, 100); - private static final Color SELECTED_POINT_COLOR = new Color(200, 0, 0); - private static final double MINOR_TICKS = 0.01; - private static final double MAJOR_TICKS = 0.1; - - private final FreeformFinSet finset; + private static final int LINE_WIDTH_FIN_PIXELS = 1; + private static final int LINE_WIDTH_BODY_PIXELS = 2; + + // the size of the boxes around each fin point vertex + private static final int LINE_WIDTH_BOX_PIXELS = 1; + private static final float BOX_WIDTH_PIXELS = 12; + private static final float SELECTED_BOX_WIDTH_PIXELS = BOX_WIDTH_PIXELS + 4; + private static final Color POINT_COLOR = new Color(100, 100, 100); + private static final Color SELECTED_POINT_COLOR = new Color(200, 0, 0); + private static final double MINOR_TICKS = 0.01; + private static final double MAJOR_TICKS = 0.1; + + private final FreeformFinSet finset; private int modID = -1; - protected Rectangle2D finBounds_m = null; - protected Rectangle2D mountBounds_m = null; - - protected final List listeners = new LinkedList(); - - private Rectangle2D.Double[] finPointHandles = null; - private int selectedIndex = -1; + protected BoundingBox finBounds_m = null; + protected BoundingBox mountBounds_m = null; + + protected final List listeners = new LinkedList<>(); + + private Rectangle2D.Double[] finPointHandles = null; + private int selectedIndex = -1; public FinPointFigure(FreeformFinSet finset) { this.finset = finset; @@ -69,19 +68,34 @@ public class FinPointFigure extends AbstractScaleFigure { setBackground(Color.WHITE); setOpaque(true); - updateFigure(); + updateFigure(); + } + + @Override + public Point getAutoZoomPoint(){ + // from canvas top/left + final Point zoomPointPx = new Point( Math.max(0, (originLocation_px.x - borderThickness_px.width)), 0); + +// System.err.println("==>> FinPointFigure.getAutoZoomPoint ==>> " + finset.getName() ); +// System.err.println(String.format(" ::scale(overall): %6.4f == %6.4f x %6.4f", scale, userScale, baseScale)); +// System.err.println(String.format(" ::ContentBounds(px): @ %d, %d [ %d x %d ]", (int)(contentBounds_m.getX()*scale), (int)(contentBounds_m.getY()*scale), (int)(contentBounds_m.getWidth()*scale), (int)(contentBounds_m.getHeight()*scale))); +// System.err.println(String.format(" ::SubjectBounds(px): @ %d, %d [ %d x %d ]", (int)(subjectBounds_m.getX()*scale), (int)(subjectBounds_m.getY()*scale), (int)(subjectBounds_m.getWidth()*scale), (int)(subjectBounds_m.getHeight()*scale))); +// System.err.println(String.format(" ::origin: @ %d, %d", originLocation_px.x, originLocation_px.y)); +// System.err.println(String.format(" ::ZoomPoint: @ %d, %d", zoomPointPx.x, zoomPointPx.y)); + + return zoomPointPx; } @Override public void paintComponent(Graphics g) { super.paintComponent(g); - Graphics2D g2 = (Graphics2D) g.create(); - - if (modID != finset.getRocket().getAerodynamicModID()) { - modID = finset.getRocket().getAerodynamicModID(); - updateTransform(); - } - + Graphics2D g2 = (Graphics2D) g.create(); + + if (modID != finset.getRocket().getAerodynamicModID()) { + modID = finset.getRocket().getAerodynamicModID(); + updateTransform(); + } + g2.transform(projection); // Set rendering hints appropriately @@ -97,212 +111,212 @@ public class FinPointFigure extends AbstractScaleFigure { paintRocketBody(g2); paintFinShape(g2); - paintFinHandles(g2); + paintFinHandles(g2); } public void paintBackgroundGrid( Graphics2D g2) { - Rectangle visible = g2.getClipBounds(); - final double x0 = visible.x - 3; - final double x1 = visible.x + visible.width + 4; - final double y0 = visible.y - 3; - final double y1 = visible.y + visible.height + 4; + Rectangle visible = g2.getClipBounds(); + final double x0 = visible.x - 3; + final double x1 = visible.x + visible.width + 4; + final double y0 = visible.y - 3; + final double y1 = visible.y + visible.height + 4; - final float grid_line_width = (float)(GRID_LINE_BASE_WIDTH_PIXELS/this.scale); - g2.setStroke(new BasicStroke( grid_line_width, - BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL)); + final float grid_line_width = (float)(GRID_LINE_BASE_WIDTH_PIXELS/this.scale); + g2.setStroke(new BasicStroke( grid_line_width, + BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL)); - Unit unit; - if (this.getParent() != null && this.getParent().getParent() instanceof ScaleScrollPane) { - unit = ((ScaleScrollPane) this.getParent().getParent()).getCurrentUnit(); - } else { - unit = UnitGroup.UNITS_LENGTH.getDefaultUnit(); - } + Unit unit; + if (this.getParent() != null && this.getParent().getParent() instanceof ScaleScrollPane) { + unit = ((ScaleScrollPane) this.getParent().getParent()).getCurrentUnit(); + } else { + unit = UnitGroup.UNITS_LENGTH.getDefaultUnit(); + } - // vertical - Tick[] verticalTicks = unit.getTicks(x0, x1, MINOR_TICKS, MAJOR_TICKS); - Line2D.Double line = new Line2D.Double(); - for (Tick t : verticalTicks) { - if (t.major) { - g2.setColor(FinPointFigure.GRID_MAJOR_LINE_COLOR); - line.setLine( t.value, y0, t.value, y1); - g2.draw(line); - }else{ - g2.setColor(FinPointFigure.GRID_MINOR_LINE_COLOR); - line.setLine( t.value, y0, t.value, y1); - g2.draw(line); - } - } + // vertical + Tick[] verticalTicks = unit.getTicks(x0, x1, MINOR_TICKS, MAJOR_TICKS); + Line2D.Double line = new Line2D.Double(); + for (Tick t : verticalTicks) { + if (t.major) { + g2.setColor(FinPointFigure.GRID_MAJOR_LINE_COLOR); + line.setLine( t.value, y0, t.value, y1); + g2.draw(line); + }else{ + g2.setColor(FinPointFigure.GRID_MINOR_LINE_COLOR); + line.setLine( t.value, y0, t.value, y1); + g2.draw(line); + } + } - // horizontal - Tick[] horizontalTicks = unit.getTicks(y0, y1, MINOR_TICKS, MAJOR_TICKS); - for (Tick t : horizontalTicks) { - if (t.major) { - g2.setColor(FinPointFigure.GRID_MAJOR_LINE_COLOR); - line.setLine( x0, t.value, x1, t.value); - g2.draw(line); - }else{ - g2.setColor(FinPointFigure.GRID_MINOR_LINE_COLOR); - line.setLine( x0, t.value, x1, t.value); - g2.draw(line); - } - } + // horizontal + Tick[] horizontalTicks = unit.getTicks(y0, y1, MINOR_TICKS, MAJOR_TICKS); + for (Tick t : horizontalTicks) { + if (t.major) { + g2.setColor(FinPointFigure.GRID_MAJOR_LINE_COLOR); + line.setLine( x0, t.value, x1, t.value); + g2.draw(line); + }else{ + g2.setColor(FinPointFigure.GRID_MINOR_LINE_COLOR); + line.setLine( x0, t.value, x1, t.value); + g2.draw(line); + } + } } - private void paintRocketBody( Graphics2D g2){ - RocketComponent comp = finset.getParent(); - if( comp instanceof Transition ){ - paintBodyTransition(g2); - }else{ - paintBodyTube(g2); - } - } + private void paintRocketBody( Graphics2D g2){ + RocketComponent comp = finset.getParent(); + if( comp instanceof Transition ){ + paintBodyTransition(g2); + }else{ + paintBodyTube(g2); + } + } - // NOTE: This function drawns relative to the reference point of the BODY component - // In other words: 0,0 == the front, foreRadius of the body component - private void paintBodyTransition( Graphics2D g2){ - - // setup lines - final float bodyLineWidth = (float) ( LINE_WIDTH_BODY_PIXELS / scale ); - g2.setStroke(new BasicStroke( bodyLineWidth, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL)); - g2.setColor(Color.BLACK); + // NOTE: This function drawns relative to the reference point of the BODY component + // In other words: 0,0 == the front, foreRadius of the body component + private void paintBodyTransition( Graphics2D g2){ - Transition body = (Transition) finset.getParent(); - final float xResolution_m = 0.01f; // distance between draw points, in meters + // setup lines + final float bodyLineWidth = (float) ( LINE_WIDTH_BODY_PIXELS / scale ); + g2.setStroke(new BasicStroke( bodyLineWidth, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL)); + g2.setColor(Color.BLACK); - final double xFinStart = finset.getAxialOffset(AxialMethod.TOP); //<< in body frame + Transition body = (Transition) finset.getParent(); + final float xResolution_m = 0.01f; // distance between draw points, in meters - // vv in fin-frame == draw-frame vv - final double xOffset = -xFinStart; - final double yOffset = -body.getRadius(xFinStart); + final double xFinStart = finset.getAxialOffset(AxialMethod.TOP); //<< in body frame - Path2D.Double bodyShape = new Path2D.Double(); - // draw front-cap: - bodyShape.moveTo( xOffset, yOffset); - bodyShape.lineTo( xOffset, yOffset + body.getForeRadius()); + // vv in fin-frame == draw-frame vv + final double xOffset = -xFinStart; + final double yOffset = -body.getRadius(xFinStart); - final float length_m = (float)( body.getLength()); - Point2D.Double cur = new Point2D.Double (); - for( double xBody = xResolution_m ; xBody < length_m; xBody += xResolution_m ){ - // xBody is distance from front of parent body - cur.x = xOffset + xBody; // offset from origin (front of fin) - cur.y = yOffset + body.getRadius( xBody); // offset from origin ( fin-front-point ) + Path2D.Double bodyShape = new Path2D.Double(); + // draw front-cap: + bodyShape.moveTo( xOffset, yOffset); + bodyShape.lineTo( xOffset, yOffset + body.getForeRadius()); - bodyShape.lineTo( cur.x, cur.y); - } + final float length_m = (float)( body.getLength()); + Point2D.Double cur = new Point2D.Double (); + for( double xBody = xResolution_m ; xBody < length_m; xBody += xResolution_m ){ + // xBody is distance from front of parent body + cur.x = xOffset + xBody; // offset from origin (front of fin) + cur.y = yOffset + body.getRadius( xBody); // offset from origin ( fin-front-point ) - // draw end-cap - bodyShape.lineTo( xOffset + length_m, yOffset + body.getAftRadius()); - bodyShape.lineTo( xOffset + length_m, yOffset); + bodyShape.lineTo( cur.x, cur.y); + } - g2.draw(bodyShape); - } + // draw end-cap + bodyShape.lineTo( xOffset + length_m, yOffset + body.getAftRadius()); + bodyShape.lineTo( xOffset + length_m, yOffset); - private void paintBodyTube( Graphics2D g2){ - // in-figure left extent - final double xFore = mountBounds_m.getMinX(); - // in-figure right extent - final double xAft = mountBounds_m.getMaxX(); - // in-figure right extent - final double yCenter = mountBounds_m.getMinY(); - - Path2D.Double shape = new Path2D.Double(); - shape.moveTo( xFore, yCenter ); - shape.lineTo( xFore, 0); // body tube fore edge - shape.lineTo( xAft, 0); // body tube side - shape.lineTo( xAft, yCenter); // body tube aft edge - - final float bodyLineWidth = (float) ( LINE_WIDTH_BODY_PIXELS / scale ); - g2.setStroke(new BasicStroke( bodyLineWidth, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL)); - g2.setColor(Color.BLACK); - g2.draw(shape); - } + g2.draw(bodyShape); + } + + private void paintBodyTube( Graphics2D g2){ + // in-figure left extent + final double xFore = mountBounds_m.min.x; + // in-figure right extent + final double xAft = mountBounds_m.max.x; + // in-figure right extent + final double yCenter = mountBounds_m.min.y; + + Path2D.Double shape = new Path2D.Double(); + shape.moveTo( xFore, yCenter ); + shape.lineTo( xFore, 0); // body tube fore edge + shape.lineTo( xAft, 0); // body tube side + shape.lineTo( xAft, yCenter); // body tube aft edge + + final float bodyLineWidth = (float) ( LINE_WIDTH_BODY_PIXELS / scale ); + g2.setStroke(new BasicStroke( bodyLineWidth, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL)); + g2.setColor(Color.BLACK); + g2.draw(shape); + } private void paintFinShape(final Graphics2D g2){ - // excludes fin tab points - final Coordinate[] drawPoints = finset.getFinPoints(); - - Path2D.Double shape = new Path2D.Double(); - Coordinate startPoint= drawPoints[0]; - shape.moveTo( startPoint.x, startPoint.y); - for (int i = 1; i < drawPoints.length; i++) { - shape.lineTo( drawPoints[i].x, drawPoints[i].y); - } - - final float finEdgeWidth_m = (float) (LINE_WIDTH_FIN_PIXELS / scale ); - g2.setStroke(new BasicStroke( finEdgeWidth_m, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL)); - g2.setColor(Color.BLACK); - g2.draw(shape); + // excludes fin tab points + final Coordinate[] drawPoints = finset.getFinPoints(); + + Path2D.Double shape = new Path2D.Double(); + Coordinate startPoint= drawPoints[0]; + shape.moveTo( startPoint.x, startPoint.y); + for (int i = 1; i < drawPoints.length; i++) { + shape.lineTo( drawPoints[i].x, drawPoints[i].y); + } + + final float finEdgeWidth_m = (float) (LINE_WIDTH_FIN_PIXELS / scale ); + g2.setStroke(new BasicStroke( finEdgeWidth_m, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL)); + g2.setColor(Color.BLACK); + g2.draw(shape); } private void paintFinHandles(final Graphics2D g2) { - // excludes fin tab points - final Coordinate[] drawPoints = finset.getFinPoints(); - - // Fin point boxes - final float boxWidth = (float) (BOX_WIDTH_PIXELS / scale ); - final float boxHalfWidth = boxWidth/2; - - final float boxEdgeWidth_m = (float) ( LINE_WIDTH_BOX_PIXELS / scale ); - g2.setStroke(new BasicStroke( boxEdgeWidth_m, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL)); - g2.setColor(POINT_COLOR); - - finPointHandles = new Rectangle2D.Double[ drawPoints.length]; - for (int currentIndex = 0; currentIndex < drawPoints.length; currentIndex++) { - Coordinate c = drawPoints[currentIndex]; - - if( currentIndex == selectedIndex ) { - final float selBoxWidth = (float) (SELECTED_BOX_WIDTH_PIXELS / scale ); - final float selBoxHalfWidth = selBoxWidth/2; + // excludes fin tab points + final Coordinate[] drawPoints = finset.getFinPoints(); - final Rectangle2D.Double selectedPointHighlight = new Rectangle2D.Double(c.x - selBoxHalfWidth, c.y - selBoxHalfWidth, selBoxWidth, selBoxWidth); + // Fin point boxes + final float boxWidth = (float) (BOX_WIDTH_PIXELS / scale ); + final float boxHalfWidth = boxWidth/2; - // switch to the highlight color - g2.setColor(SELECTED_POINT_COLOR); - g2.draw(selectedPointHighlight); - - // reset to the normal color - g2.setColor(POINT_COLOR); - } + final float boxEdgeWidth_m = (float) ( LINE_WIDTH_BOX_PIXELS / scale ); + g2.setStroke(new BasicStroke( boxEdgeWidth_m, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL)); + g2.setColor(POINT_COLOR); - // normal boxes - finPointHandles[currentIndex] = new Rectangle2D.Double(c.x - boxHalfWidth, c.y - boxHalfWidth, boxWidth, boxWidth); - - g2.draw(finPointHandles[currentIndex]); - } - } + finPointHandles = new Rectangle2D.Double[ drawPoints.length]; + for (int currentIndex = 0; currentIndex < drawPoints.length; currentIndex++) { + Coordinate c = drawPoints[currentIndex]; + + if( currentIndex == selectedIndex ) { + final float selBoxWidth = (float) (SELECTED_BOX_WIDTH_PIXELS / scale ); + final float selBoxHalfWidth = selBoxWidth/2; + + final Rectangle2D.Double selectedPointHighlight = new Rectangle2D.Double(c.x - selBoxHalfWidth, c.y - selBoxHalfWidth, selBoxWidth, selBoxWidth); + + // switch to the highlight color + g2.setColor(SELECTED_POINT_COLOR); + g2.draw(selectedPointHighlight); + + // reset to the normal color + g2.setColor(POINT_COLOR); + } + + // normal boxes + finPointHandles[currentIndex] = new Rectangle2D.Double(c.x - boxHalfWidth, c.y - boxHalfWidth, boxWidth, boxWidth); + + g2.draw(finPointHandles[currentIndex]); + } + } private Point2D.Double getPoint( final int x, final int y){ - if (finPointHandles == null) - return null; - - // Calculate point in shapes' coordinates - Point2D.Double p = new Point2D.Double(x, y); - try { - projection.inverseTransform(p, p); - return p; - } catch (NoninvertibleTransformException e) { - return null; - } + if (finPointHandles == null) + return null; + + // Calculate point in shapes' coordinates + Point2D.Double p = new Point2D.Double(x, y); + try { + projection.inverseTransform(p, p); + return p; + } catch (NoninvertibleTransformException e) { + return null; + } } - public int getIndexByPoint(final int x, final int y) { - final Point2D.Double p = getPoint(x,y); - if (p == null) - return -1; - - for (int i = 0; i < finPointHandles.length; i++) { - if (finPointHandles[i].contains(p)) { - return i; - } - } - - return -1; - } + public int getIndexByPoint(final int x, final int y) { + final Point2D.Double p = getPoint(x,y); + if (p == null) + return -1; + + for (int i = 0; i < finPointHandles.length; i++) { + if (finPointHandles[i].contains(p)) { + return i; + } + } + + return -1; + } public int getSegmentByPoint(final int x, final int y) { - final Point2D.Double p = getPoint(x,y); - if (p == null) - return -1; + final Point2D.Double p = getPoint(x,y); + if (p == null) + return -1; final double threshold = BOX_WIDTH_PIXELS / scale; @@ -318,15 +332,15 @@ public class FinPointFigure extends AbstractScaleFigure { // Distance to an infinite line, defined by two points: // (For a more in-depth explanation, see wikipedia: https://en.wikipedia.org/wiki/Distance_from_a_point_to_a_line ) double x0 = p.x; - double y0 = p.y; - final double distanceToLine = Math.abs((y2 - y1)*x0 - (x2-x1)*y0 + x2*y1 - y2*x1)/segmentLength; - - final double distanceToStart = MathUtil.hypot(x1-x0, y1-y0); - final double distanceToEnd = MathUtil.hypot(x2-x0, y2-y0); - final boolean withinSegment = (distanceToStart < segmentLength && distanceToEnd < segmentLength); - + double y0 = p.y; + final double distanceToLine = Math.abs((y2 - y1)*x0 - (x2-x1)*y0 + x2*y1 - y2*x1)/segmentLength; + + final double distanceToStart = MathUtil.hypot(x1-x0, y1-y0); + final double distanceToEnd = MathUtil.hypot(x2-x0, y2-y0); + final boolean withinSegment = (distanceToStart < segmentLength && distanceToEnd < segmentLength); + if ( distanceToLine < threshold && withinSegment){ - return i; + return i; } } @@ -347,57 +361,61 @@ public class FinPointFigure extends AbstractScaleFigure { return p; } - public Dimension getSubjectOrigin() { + public Point getSubjectOrigin() { if (modID != finset.getRocket().getAerodynamicModID()) { modID = finset.getRocket().getAerodynamicModID(); updateTransform(); } - return new Dimension(originLocation_px.width, originLocation_px.height); - } - - @Override - protected void updateSubjectDimensions(){ - - // update subject (i.e. Fin) bounds - finBounds_m = new BoundingBox().update(finset.getFinPoints()).toRectangle(); - - // update to bound the parent body: - SymmetricComponent parent = (SymmetricComponent)this.finset.getParent(); - final double xFinFront = finset.getAxialOffset(AxialMethod.TOP); - final double xParent = -xFinFront; - final double yParent = -parent.getRadius(xParent); // from parent centerline to fin front. - final double rParent = Math.max(parent.getForeRadius(), parent.getAftRadius()); - mountBounds_m = new Rectangle2D.Double( xParent, yParent, parent.getLength(), rParent); - - final double xMinBounds = Math.min(xParent, finBounds_m.getMinX()); - final double yMinBounds = Math.min(xParent, finBounds_m.getMinY()); - final double subjectWidth = Math.max( xFinFront + finBounds_m.getWidth(), parent.getLength()); - final double subjectHeight = Math.max( 2*rParent, rParent + finBounds_m.getHeight()); - subjectBounds_m = new Rectangle2D.Double( xMinBounds, yMinBounds, subjectWidth, subjectHeight); + return new Point(originLocation_px.x, originLocation_px.y); } @Override - protected void updateCanvasOrigin() { - final int finHeight = (int)(finBounds_m.getHeight()*scale); - final int mountHeight = (int)(mountBounds_m.getHeight()*scale); - final int finFrontX = (int)(subjectBounds_m.getX()*scale); - final int subjectHeight = (int)(subjectBounds_m.getHeight()*scale); - - originLocation_px.width = borderThickness_px.width - finFrontX; - - if( visibleBounds_px.height > (subjectHeight+ 2*borderThickness_px.height)) { - originLocation_px.height = getHeight() - mountHeight - borderThickness_px.height; - }else { - originLocation_px.height = borderThickness_px.height + finHeight; - } - } + protected void updateSubjectDimensions(){ + // update subject (i.e. Fin) bounds + finBounds_m = new BoundingBox().update(finset.getFinPoints()); + subjectBounds_m = finBounds_m.toRectangle(); - public void resetSelectedIndex() { - this.selectedIndex = -1; - } + // update to bound the parent body: + SymmetricComponent parent = (SymmetricComponent)this.finset.getParent(); + final double xFinFront = finset.getAxialOffset(AxialMethod.TOP); + final double xParent = -xFinFront; + final double yParent = -parent.getRadius(xParent); // from fin-front to parent centerline + final double rParent = Math.max(parent.getForeRadius(), parent.getAftRadius()); - public void setSelectedIndex(final int newIndex) { - this.selectedIndex = newIndex; - } + mountBounds_m = new BoundingBox() + .update(new Coordinate(xParent, yParent, 0)) + .update(new Coordinate(xParent + parent.getLength(), yParent + rParent)); + + final BoundingBox combinedBounds = new BoundingBox().update(finBounds_m).update(mountBounds_m); + + contentBounds_m = combinedBounds.toRectangle(); + } + + @Override + protected void updateCanvasOrigin() { + final int finHeightPx = (int)(finBounds_m.max.y*scale); + final int mountHeightPx = (int)(mountBounds_m.span().y*scale); + // this is non-intuitive: it's an offset _from_ the origin(0,0) _to_ the lower-left of the content -- + // because the canvas is drawn from that lower-left corner of the content, and the fin-front + // is fixed-- by definition-- to the origin. + final int finFrontPx = -(int)(contentBounds_m.getX()*scale); // pixels from left-border to fin-front + final int contentHeightPx = (int)(contentBounds_m.getHeight()*scale); + + originLocation_px.x = borderThickness_px.width + finFrontPx; + + if( visibleBounds_px.height > (contentHeightPx + 2*borderThickness_px.height)){ + originLocation_px.y = getHeight() - mountHeightPx - borderThickness_px.height; + }else { + originLocation_px.y = borderThickness_px.height + finHeightPx; + } + } + + public void resetSelectedIndex() { + this.selectedIndex = -1; + } + + public void setSelectedIndex(final int newIndex) { + this.selectedIndex = newIndex; + } } diff --git a/swing/src/net/sf/openrocket/gui/scalefigure/RocketFigure.java b/swing/src/net/sf/openrocket/gui/scalefigure/RocketFigure.java index 188d70ad2..6314b303b 100644 --- a/swing/src/net/sf/openrocket/gui/scalefigure/RocketFigure.java +++ b/swing/src/net/sf/openrocket/gui/scalefigure/RocketFigure.java @@ -1,14 +1,7 @@ package net.sf.openrocket.gui.scalefigure; -import java.awt.BasicStroke; -import java.awt.Color; -import java.awt.Dimension; -import java.awt.Graphics; -import java.awt.Graphics2D; -import java.awt.Rectangle; -import java.awt.RenderingHints; -import java.awt.Shape; +import java.awt.*; import java.awt.geom.AffineTransform; import java.awt.geom.Ellipse2D; import java.awt.geom.NoninvertibleTransformException; @@ -65,7 +58,7 @@ public class RocketFigure extends AbstractScaleFigure { public static final double SELECTED_WIDTH = 2.0; - private Rocket rocket; + final private Rocket rocket; private RocketComponent[] selection = new RocketComponent[0]; @@ -93,10 +86,15 @@ public class RocketFigure extends AbstractScaleFigure { this.rotation = 0.0; this.axialRotation = Transformation.rotate_x(0.0); - + updateFigure(); } - + + public Point getAutoZoomPoint(){ + return new Point( Math.max(0, originLocation_px.x - borderThickness_px.width), + Math.max(0, - borderThickness_px.height)); + } + public RocketComponent[] getSelection() { return selection; } @@ -361,7 +359,7 @@ public class RocketFigure extends AbstractScaleFigure { * Gets the shapes required to draw the component. * * @param component - * @param params + * * @return the ArrayList containing all the shapes to draw. */ private static ArrayList addThisShape( @@ -407,33 +405,38 @@ public class RocketFigure extends AbstractScaleFigure { } - /** - * Gets the bounds of the drawn subject in Model-Space - * - * i.e. the maximum extents in the selected dimensions. - * The bounds are stored in the variables minX, maxX and maxR. - * - * @return - */ - @Override - protected void updateSubjectDimensions() { - // calculate bounds, and store in class variables - final BoundingBox bounds = rocket.getSelectedConfiguration().getBoundingBox(); - final double maxR = Math.max(Math.hypot(bounds.min.y, bounds.min.z), - Math.hypot(bounds.max.y, bounds.max.z)); - - switch (currentViewType) { - case SideView: - subjectBounds_m = new Rectangle2D.Double(bounds.min.x, -maxR, bounds.span().x, 2 * maxR); - break; - case BackView: - subjectBounds_m = new Rectangle2D.Double(-maxR, -maxR, 2 * maxR, 2 * maxR); - break; - default: - throw new BugException("Illegal figure type = " + currentViewType); - } - } - + /** + * Gets the bounds of the drawn subject in Model-Space + * + * i.e. the maximum extents in the selected dimensions. + * The bounds are stored in the variables minX, maxX and maxR. + * + * @return + */ + @Override + protected void updateSubjectDimensions() { + // calculate bounds, and store in class variables + BoundingBox newBounds = rocket.getSelectedConfiguration().getBoundingBox(); + if(newBounds.isEmpty()) + newBounds = new BoundingBox(Coordinate.ZERO,Coordinate.X_UNIT); + + final double maxR = Math.max( Math.hypot(newBounds.min.y, newBounds.min.z), + Math.hypot(newBounds.max.y, newBounds.max.z)); + + switch (currentViewType) { + case SideView: + subjectBounds_m = new Rectangle2D.Double(newBounds.min.x, -maxR, newBounds.span().x, 2 * maxR); + break; + case BackView: + subjectBounds_m = new Rectangle2D.Double(-maxR, -maxR, 2 * maxR, 2 * maxR); + break; + default: + throw new BugException("Illegal figure type = " + currentViewType); + } + // for a rocket, these are the same + contentBounds_m = subjectBounds_m; + } + /** * Calculates the necessary size of the figure and set the PreferredSize * property accordingly. @@ -447,13 +450,12 @@ public class RocketFigure extends AbstractScaleFigure { if (currentViewType == RocketPanel.VIEW_TYPE.BackView){ final int newOriginX = borderThickness_px.width + Math.max(getWidth(), subjectWidth + 2*borderThickness_px.width)/ 2; final int newOriginY = borderThickness_px.height + getHeight() / 2; - - originLocation_px = new Dimension(newOriginX, newOriginY); + + originLocation_px = new Point(newOriginX, newOriginY); }else if (currentViewType == RocketPanel.VIEW_TYPE.SideView){ final int newOriginX = borderThickness_px.width - subjectFront; final int newOriginY = Math.max(getHeight(), subjectHeight + 2*borderThickness_px.height )/ 2; - - originLocation_px = new Dimension(newOriginX, newOriginY); + originLocation_px = new Point(newOriginX, newOriginY); } } diff --git a/swing/src/net/sf/openrocket/gui/scalefigure/ScaleScrollPane.java b/swing/src/net/sf/openrocket/gui/scalefigure/ScaleScrollPane.java index 3a16cf6e9..e5d665889 100644 --- a/swing/src/net/sf/openrocket/gui/scalefigure/ScaleScrollPane.java +++ b/swing/src/net/sf/openrocket/gui/scalefigure/ScaleScrollPane.java @@ -6,6 +6,7 @@ import java.awt.Dimension; import java.awt.Font; import java.awt.Graphics; import java.awt.Graphics2D; +import java.awt.Point; import java.awt.Rectangle; import java.awt.RenderingHints; import java.awt.event.ComponentAdapter; @@ -13,6 +14,7 @@ import java.awt.event.ComponentEvent; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.awt.event.MouseMotionListener; +import java.awt.geom.Rectangle2D; import java.util.EventObject; import javax.swing.BorderFactory; @@ -34,7 +36,7 @@ import net.sf.openrocket.util.StateChangeListener; /** - * A scroll pane that holds a {@link ScaleFigure} and includes rulers that show + * A scroll pane that holds a {@link AbstractScaleFigure} and includes rulers that show * natural units. The figure can be moved by dragging on the figure. *

* This class implements both MouseListener and @@ -68,7 +70,6 @@ public class ScaleScrollPane extends JScrollPane * Create a scale scroll pane. * * @param component the component to contain (must implement ScaleFigure) - * @param allowFit whether automatic fitting of the figure is allowed */ public ScaleScrollPane(final JComponent component) { super(component); @@ -99,8 +100,13 @@ public class ScaleScrollPane extends JScrollPane this.setBorder(BorderFactory.createLineBorder(Color.LIGHT_GRAY)); setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED); - setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED); - + getHorizontalScrollBar().setUnitIncrement(50); + //getHorizontalScrollBar().setBlockIncrement(viewport.getWidth()); // the default value is good + + setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED); + getVerticalScrollBar().setUnitIncrement(50); + //getVerticalScrollBar().setBlockIncrement(viewport.getHeight()); // the default value is good + viewport.addMouseListener(this); viewport.addMouseMotionListener(this); @@ -148,12 +154,17 @@ public class ScaleScrollPane extends JScrollPane this.fit = shouldFit; if (shouldFit) { validate(); - - Dimension view = viewport.getExtentSize(); - figure.scaleTo(view); - this.firePropertyChange( USER_SCALE_PROPERTY, 1.0, figure.getUserScale()); - revalidate(); + Dimension view = viewport.getExtentSize(); + figure.scaleTo(view); + + final Point zoomPoint = figure.getAutoZoomPoint(); + final Rectangle zoomRectangle = new Rectangle(zoomPoint.x, zoomPoint.y, (int)(view.getWidth()), (int)(view.getHeight())); +// System.err.println(String.format("::zoom: @ %d, %d [ %d x %d ]", zoomRectangle.x, zoomRectangle.y, zoomRectangle.width, zoomRectangle.height)); + figure.scrollRectToVisible(zoomRectangle); + + figure.invalidate(); + revalidate(); } } @@ -166,13 +177,13 @@ public class ScaleScrollPane extends JScrollPane if( MathUtil.equals(newScale, figure.getUserScale(), 0.01)){ return; } - + // if explicitly setting a zoom level, turn off fitting - this.fit = false; + this.fit = false; Dimension view = viewport.getExtentSize(); - figure.scaleTo(newScale, view); - - revalidate(); + figure.scaleTo(newScale, view); + + revalidate(); } @@ -233,7 +244,7 @@ public class ScaleScrollPane extends JScrollPane dragStartX = e.getX(); dragStartY = e.getY(); - + viewport.scrollRectToVisible(dragRectangle); } @@ -286,23 +297,23 @@ public class ScaleScrollPane extends JScrollPane } private double fromPx(final int px) { - Dimension origin = figure.getSubjectOrigin(); + final Point origin = figure.getSubjectOrigin(); double realValue = Double.NaN; if (orientation == HORIZONTAL) { - realValue = px - origin.width; + realValue = px - origin.x; } else { - realValue = origin.height - px; + realValue = origin.y - px; } return realValue / figure.getAbsoluteScale(); } private int toPx(final double value) { - final Dimension origin = figure.getSubjectOrigin(); + final Point origin = figure.getSubjectOrigin(); final int px = (int) (value * figure.getAbsoluteScale() + 0.5); if (orientation == HORIZONTAL) { - return (px + origin.width); + return (px + origin.x); } else { - return (origin.height - px); + return (origin.y - px); } } @@ -316,9 +327,8 @@ public class ScaleScrollPane extends JScrollPane // this function doesn't reliably update all the time, so we'll draw everything for the entire canvas, // and let the JVM drawing algorithms figure out what should be drawn. - // - Rectangle area = ScaleScrollPane.this.getViewport().getViewRect(); - + Rectangle area = viewport.getViewRect(); + // Fill area with background color g2.setColor(getBackground()); g2.fillRect(area.x, area.y, area.width, area.height + 100);