Merge pull request #686 from teyrana/476/fff-auto-scroll

[fixes #502][Fixes #476] Fixes auto-scrolling of FinPointFigure bounds.
This commit is contained in:
Daniel Williams 2020-07-18 12:00:51 -04:00 committed by GitHub
commit 99f758c744
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 691 additions and 636 deletions

View File

@ -1,10 +1,12 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Openrocket UI Jar" type="JarApplication">
<option name="JAR_PATH" value="build/jar/OpenRocket.jar" />
<option name="JAR_PATH" value="$PROJECT_DIR$/build/jar/OpenRocket.jar" />
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="true" />
<option name="ALTERNATIVE_JRE_PATH" value="11" />
<method v="2">
<option name="BuildArtifacts" enabled="true" />
<option name="BuildArtifacts" enabled="true">
<artifact name="openrocket:jar" />
</option>
</method>
</configuration>
</component>

View File

@ -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<Coordinate> getComponentBounds() {
Collection<Coordinate> bounds = new ArrayList<Coordinate>(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.

View File

@ -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 <sampo.niskanen@iki.fi>
*/
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<Coordinate> getComponentBounds() {
Collection<Coordinate> bounds = new ArrayList<Coordinate>(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.

View File

@ -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();
}

View File

@ -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 <sampo.niskanen@iki.fi>
*/
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<Coordinate> 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).
*/

View File

@ -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<Coordinate> getComponentBounds() {
Collection<Coordinate> bounds = new ArrayList<Coordinate>(8);
Collection<Coordinate> bounds = new ArrayList<>(8);
// should simply return this component's bounds in this component's body frame.

View File

@ -550,59 +550,75 @@ public class FlightConfiguration implements FlightConfigurableParameter<FlightCo
* in the current configuration.
*/
private void calculateBounds(){
BoundingBox bounds = new BoundingBox();
BoundingBox rocketBounds = new BoundingBox();
InstanceMap map = getActiveInstances();
for (Map.Entry<RocketComponent, java.util.ArrayList<InstanceContext>> entry : map.entrySet()) {
RocketComponent component = entry.getKey();
BoundingBox componentBounds = new BoundingBox();
List<InstanceContext> contexts = entry.getValue();
Collection<Coordinate> coordinates = new ArrayList<Coordinate>();
/* 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<Coordinate> transformedCoords = new ArrayList<Coordinate>();
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<Coordinate> 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<Coordinate> 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<FlightCo
if (getActiveStages().isEmpty()) {
cachedLength = 0;
}
cachedBounds.update( bounds );
cachedBounds.update( rocketBounds );
}
/**

View File

@ -203,7 +203,7 @@ public class FreeformFinSet extends FinSet {
* @param yRequest the y-coordinate.
*/
public void setPoint(final int index, final double xRequest, final double yRequest) {
final Coordinate revertPoint = points.get(index);
if(null != this.getParent()) {
final Coordinate prior = points.get(index);
points.set(index, new Coordinate(xRequest, yRequest));
@ -215,11 +215,9 @@ public class FreeformFinSet extends FinSet {
update();
// this maps the last index and the next-to-last-index to the same 'testIndex'
int testIndex = Math.min(index, (points.size() - 2));
if (intersects(testIndex)) {
if (intersects()) {
// intersection found! log error and abort!
log.error(String.format("ERROR: found an intersection while setting fin point #%d to [%6.4g, %6.4g] <body frame> : 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;
}
}

View File

@ -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<Coordinate> getComponentBounds() {
Collection<Coordinate> bounds = new ArrayList<Coordinate>(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;

View File

@ -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;

View File

@ -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 <sampo.niskanen@iki.fi>
*/
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.

View File

@ -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<Coordinate> toCollection(){
Collection<Coordinate> toReturn = new ArrayList<Coordinate>();
Collection<Coordinate> toReturn = new ArrayList<>();
toReturn.add( this.max);
toReturn.add( this.min);
return toReturn;

View File

@ -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);

View File

@ -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);

View File

@ -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);
}

View File

@ -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;

View File

@ -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<TrapezoidFinSet>) 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<TrapezoidFinSet>) 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);
}
}

View File

@ -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);
}
}

View File

@ -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);

View File

@ -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();
}
}

View File

@ -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<EventListener> listeners = new LinkedList<EventListener>();
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);

View File

@ -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<StateChangeListener> listeners = new LinkedList<StateChangeListener>();
private Rectangle2D.Double[] finPointHandles = null;
private int selectedIndex = -1;
protected BoundingBox finBounds_m = null;
protected BoundingBox mountBounds_m = null;
protected final List<StateChangeListener> 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;
}
}

View File

@ -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 <code>ArrayList</code> containing all the shapes to draw.
*/
private static ArrayList<RocketComponentShape> 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);
}
}

View File

@ -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.
* <p>
* This class implements both <code>MouseListener</code> 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);