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:
commit
99f758c744
6
.idea/runConfigurations/Openrocket_UI_Jar.xml
generated
6
.idea/runConfigurations/Openrocket_UI_Jar.xml
generated
@ -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>
|
@ -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.
|
||||
|
@ -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.
|
||||
|
15
core/src/net/sf/openrocket/rocketcomponent/BoxBounded.java
Normal file
15
core/src/net/sf/openrocket/rocketcomponent/BoxBounded.java
Normal 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();
|
||||
|
||||
}
|
@ -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).
|
||||
*/
|
||||
|
@ -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.
|
||||
|
||||
|
@ -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 );
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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.
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
Loading…
x
Reference in New Issue
Block a user