[Feature] Freeform Fins may not be attched to variable-shaped body components
- Fins may be attached to Transitions (and subclass NoseCones ) [Fix] FinSet now implements the Ring-Instanceable interface [Refactor] Rocket inherits from ComponentAssembly instead of RocketComponent [Fix][Refactor] Fin tabs are now correctly validated upon change [Fix] Fin tabs are now corrected to be no-bigger-than their fins [Refactor] FinSet.getBodyRadius(..) now requires an argument [Fix] restricted fin tab positioning to be strictly top/middle/bottom [Refactor] Reimplement FreeformFinSet.setPoint(...) [Fix] Prevent Freeform Fins movement past parent's top/front [bugfix] Fins are now addable to transitions from the GUI [Fix] Fins, Transitions are now drawn correctly in fin-design window [Minor] Added makeV2 rocket to TestRockets [fix] getRootPoints() impl & test
This commit is contained in:
parent
b268d3aa59
commit
166d358c14
@ -473,7 +473,7 @@ public class BarrowmanCalculator extends AbstractAerodynamicCalculator {
|
||||
FinSet f = (FinSet) c;
|
||||
double mac = ((FinSetCalc) calcMap.get(c)).getMACLength();
|
||||
double cd = componentCf * (1 + 2 * f.getThickness() / mac) *
|
||||
2 * f.getFinCount() * f.getFinArea();
|
||||
2 * f.getFinCount() * f.getPlanformArea();
|
||||
finFriction += cd;
|
||||
|
||||
if (map != null) {
|
||||
@ -757,7 +757,7 @@ public class BarrowmanCalculator extends AbstractAerodynamicCalculator {
|
||||
for (RocketComponent c : configuration.getActiveComponents()) {
|
||||
if (c instanceof FinSet) {
|
||||
FinSet f = (FinSet) c;
|
||||
mul += 0.6 * Math.min(f.getFinCount(), 4) * f.getFinArea() *
|
||||
mul += 0.6 * Math.min(f.getFinCount(), 4) * f.getPlanformArea() *
|
||||
MathUtil.pow3(Math.abs(f.toAbsolute(new Coordinate(
|
||||
((FinSetCalc) calcMap.get(f)).getMidchordPos()))[0].x
|
||||
- cgx)) /
|
||||
|
@ -62,16 +62,19 @@ public class FinSetCalc extends RocketComponentCalc {
|
||||
public FinSetCalc(FinSet component) {
|
||||
super(component);
|
||||
|
||||
thickness = component.getThickness();
|
||||
bodyRadius = component.getBodyRadius();
|
||||
finCount = component.getFinCount();
|
||||
baseRotation = component.getBaseRotation();
|
||||
cantAngle = component.getCantAngle();
|
||||
span = component.getSpan();
|
||||
finArea = component.getFinArea();
|
||||
crossSection = component.getCrossSection();
|
||||
FinSet fin = (FinSet) component;
|
||||
|
||||
thickness = fin.getThickness();
|
||||
bodyRadius = fin.getFinFront().y;
|
||||
finCount = fin.getFinCount();
|
||||
|
||||
calculateFinGeometry(component);
|
||||
baseRotation = fin.getBaseRotation();
|
||||
cantAngle = fin.getCantAngle();
|
||||
span = fin.getSpan();
|
||||
finArea = fin.getPlanformArea();
|
||||
crossSection = fin.getCrossSection();
|
||||
|
||||
calculateFinGeometry(fin);
|
||||
calculatePoly();
|
||||
calculateInterferenceFinCount(component);
|
||||
}
|
||||
@ -246,7 +249,7 @@ public class FinSetCalc extends RocketComponentCalc {
|
||||
protected void calculateFinGeometry(FinSet component) {
|
||||
|
||||
span = component.getSpan();
|
||||
finArea = component.getFinArea();
|
||||
finArea = component.getPlanformArea();
|
||||
ar = 2 * pow2(span) / finArea;
|
||||
|
||||
Coordinate[] points = component.getFinPoints();
|
||||
@ -339,7 +342,7 @@ public class FinSetCalc extends RocketComponentCalc {
|
||||
cosGammaLead = 0;
|
||||
rollSum = 0;
|
||||
double area = 0;
|
||||
double radius = component.getBodyRadius();
|
||||
double radius = component.getFinFront().y;
|
||||
|
||||
final double dy = span / (DIVISIONS - 1);
|
||||
for (int i = 0; i < DIVISIONS; i++) {
|
||||
|
@ -3,6 +3,8 @@ package net.sf.openrocket.file.openrocket.importt;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
|
||||
import org.xml.sax.SAXException;
|
||||
|
||||
import net.sf.openrocket.aerodynamics.Warning;
|
||||
import net.sf.openrocket.aerodynamics.WarningSet;
|
||||
import net.sf.openrocket.file.DocumentLoadingContext;
|
||||
@ -10,11 +12,8 @@ import net.sf.openrocket.file.simplesax.AbstractElementHandler;
|
||||
import net.sf.openrocket.file.simplesax.ElementHandler;
|
||||
import net.sf.openrocket.file.simplesax.PlainTextHandler;
|
||||
import net.sf.openrocket.rocketcomponent.FreeformFinSet;
|
||||
import net.sf.openrocket.rocketcomponent.IllegalFinPointException;
|
||||
import net.sf.openrocket.util.Coordinate;
|
||||
|
||||
import org.xml.sax.SAXException;
|
||||
|
||||
/**
|
||||
* A handler that reads the <point> specifications within the freeformfinset's
|
||||
* <finpoints> elements.
|
||||
@ -62,10 +61,7 @@ class FinSetPointHandler extends AbstractElementHandler {
|
||||
@Override
|
||||
public void endHandler(String element, HashMap<String, String> attributes,
|
||||
String content, WarningSet warnings) {
|
||||
try {
|
||||
finset.setPoints(coordinates.toArray(new Coordinate[0]));
|
||||
} catch (IllegalFinPointException e) {
|
||||
warnings.add(Warning.fromString("Freeform fin set point definitions illegal, ignoring."));
|
||||
}
|
||||
finset.setPoints(coordinates.toArray(new Coordinate[0]));
|
||||
|
||||
}
|
||||
}
|
@ -5,13 +5,13 @@ import java.util.HashMap;
|
||||
import net.sf.openrocket.aerodynamics.WarningSet;
|
||||
import net.sf.openrocket.rocketcomponent.FinSet;
|
||||
import net.sf.openrocket.rocketcomponent.RocketComponent;
|
||||
import net.sf.openrocket.rocketcomponent.FinSet.TabRelativePosition;
|
||||
import net.sf.openrocket.rocketcomponent.position.*;
|
||||
import net.sf.openrocket.util.Reflection;
|
||||
|
||||
class FinTabPositionSetter extends DoubleSetter {
|
||||
|
||||
public FinTabPositionSetter() {
|
||||
super(Reflection.findMethod(FinSet.class, "setTabShift", double.class));
|
||||
super(Reflection.findMethod(FinSet.class, "setTabOffset", double.class));
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -23,23 +23,30 @@ class FinTabPositionSetter extends DoubleSetter {
|
||||
}
|
||||
|
||||
String relative = attributes.get("relativeto");
|
||||
FinSet.TabRelativePosition position =
|
||||
(TabRelativePosition) DocumentConfig.findEnum(relative,
|
||||
FinSet.TabRelativePosition.class);
|
||||
|
||||
if (position != null) {
|
||||
|
||||
((FinSet) c).setTabRelativePosition(position);
|
||||
|
||||
if (relative == null) {
|
||||
warnings.add("Required attribute 'relativeto' not found for fin tab position.");
|
||||
} else {
|
||||
if (relative == null) {
|
||||
warnings.add("Required attribute 'relativeto' not found for fin tab position.");
|
||||
} else {
|
||||
warnings.add("Illegal attribute value '" + relative + "' encountered.");
|
||||
// translate from old enum names to current enum names
|
||||
if( relative.contains("front")){
|
||||
relative = "top";
|
||||
}else if( relative.contains("center")){
|
||||
relative = "middle";
|
||||
}else if( relative.contains("end")){
|
||||
relative = "bottom";
|
||||
}
|
||||
|
||||
AxialMethod position = (AxialMethod) DocumentConfig.findEnum(relative, AxialMethod.class);
|
||||
|
||||
if( null == position ){
|
||||
warnings.add("Illegal attribute value '" + relative + "' encountered.");
|
||||
}else{
|
||||
((FinSet) c).setTabOffsetMethod(position);
|
||||
super.set(c, s, attributes, warnings);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
super.set(c, s, attributes, warnings);
|
||||
}
|
||||
|
||||
|
||||
|
@ -29,8 +29,8 @@ public class FinSetSaver extends ExternalComponentSaver {
|
||||
elements.add("<tabheight>" + fins.getTabHeight() + "</tabheight>");
|
||||
elements.add("<tablength>" + fins.getTabLength() + "</tablength>");
|
||||
elements.add("<tabposition relativeto=\"" +
|
||||
fins.getTabRelativePosition().name().toLowerCase(Locale.ENGLISH) + "\">" +
|
||||
fins.getTabShift() + "</tabposition>");
|
||||
fins.getTabOffsetMethod().name().toLowerCase(Locale.ENGLISH) + "\">" +
|
||||
fins.getTabFrontEdge() + "</tabposition>");
|
||||
|
||||
}
|
||||
|
||||
|
@ -64,7 +64,7 @@ public class FinSetDTO extends BasePartDTO {
|
||||
setCantAngle(theORFinSet.getCantAngle());
|
||||
setTabDepth(theORFinSet.getTabHeight() * RocksimCommonConstants.ROCKSIM_TO_OPENROCKET_LENGTH);
|
||||
setTabLength(theORFinSet.getTabLength() * RocksimCommonConstants.ROCKSIM_TO_OPENROCKET_LENGTH);
|
||||
setTabOffset(theORFinSet.getTabShift() * RocksimCommonConstants.ROCKSIM_TO_OPENROCKET_LENGTH);
|
||||
setTabOffset(theORFinSet.getTabOffset() * RocksimCommonConstants.ROCKSIM_TO_OPENROCKET_LENGTH);
|
||||
setThickness(theORFinSet.getThickness() * RocksimCommonConstants.ROCKSIM_TO_OPENROCKET_LENGTH);
|
||||
|
||||
setRadialAngle(theORFinSet.getBaseRotation());
|
||||
|
@ -8,6 +8,8 @@ import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
import org.xml.sax.SAXException;
|
||||
|
||||
import net.sf.openrocket.aerodynamics.WarningSet;
|
||||
import net.sf.openrocket.file.DocumentLoadingContext;
|
||||
import net.sf.openrocket.file.rocksim.RocksimCommonConstants;
|
||||
@ -21,14 +23,11 @@ import net.sf.openrocket.rocketcomponent.EllipticalFinSet;
|
||||
import net.sf.openrocket.rocketcomponent.ExternalComponent;
|
||||
import net.sf.openrocket.rocketcomponent.FinSet;
|
||||
import net.sf.openrocket.rocketcomponent.FreeformFinSet;
|
||||
import net.sf.openrocket.rocketcomponent.IllegalFinPointException;
|
||||
import net.sf.openrocket.rocketcomponent.RocketComponent;
|
||||
import net.sf.openrocket.rocketcomponent.TrapezoidFinSet;
|
||||
import net.sf.openrocket.rocketcomponent.position.AxialMethod;
|
||||
import net.sf.openrocket.util.Coordinate;
|
||||
|
||||
import org.xml.sax.SAXException;
|
||||
|
||||
/**
|
||||
* A SAX handler for Rocksim fin sets. Because the type of fin may not be known first (in Rocksim file format, the fin
|
||||
* shape type is in the middle of the XML structure), and because we're using SAX not DOM, all of the fin
|
||||
@ -73,7 +72,7 @@ class FinSetHandler extends AbstractElementHandler {
|
||||
/**
|
||||
* The length of the mid-chord (aka height).
|
||||
*/
|
||||
@SuppressWarnings("unused") // spoiler: field IS actually used; eclipse complains anyway.
|
||||
@SuppressWarnings("unused") // stored from file, but not used.
|
||||
private double midChordLen = 0.0d;
|
||||
|
||||
/**
|
||||
@ -304,11 +303,8 @@ class FinSetHandler extends AbstractElementHandler {
|
||||
else if (shapeCode == 2) {
|
||||
|
||||
result = new FreeformFinSet();
|
||||
try {
|
||||
((FreeformFinSet) result).setPoints(toCoordinates(pointList, warnings));
|
||||
} catch (IllegalFinPointException e) {
|
||||
warnings.add("Illegal fin point set. " + e.getMessage() + " Ignoring.");
|
||||
}
|
||||
((FreeformFinSet) result).setPoints(toCoordinates(pointList, warnings));
|
||||
|
||||
}
|
||||
else {
|
||||
return null;
|
||||
@ -318,10 +314,10 @@ class FinSetHandler extends AbstractElementHandler {
|
||||
result.setFinCount(finCount);
|
||||
result.setFinish(finish);
|
||||
//All TTW tabs in Rocksim are relative to the front of the fin.
|
||||
result.setTabRelativePosition(FinSet.TabRelativePosition.FRONT);
|
||||
result.setTabOffsetMethod( AxialMethod.TOP);
|
||||
result.setTabHeight(tabDepth);
|
||||
result.setTabLength(tabLength);
|
||||
result.setTabShift(taboffset);
|
||||
result.setTabOffset(taboffset);
|
||||
result.setBaseRotation(radialAngle);
|
||||
result.setCrossSection(convertTipShapeCode(tipShapeCode));
|
||||
result.setAxialMethod(axialMethod);
|
||||
|
@ -243,10 +243,7 @@ public class MassCalculation {
|
||||
* Returns the mass and inertia data for this component and all subcomponents.
|
||||
* The inertia is returned relative to the CG, and the CG is in the coordinates
|
||||
* of the specified component, not global coordinates.
|
||||
*
|
||||
* @param calculation - i/o parameter to specifies the calculation parameters, and
|
||||
* the instance returned with the calculation's tree data.
|
||||
*
|
||||
*
|
||||
*/
|
||||
/* package-scope */ MassCalculation calculateAssembly(){
|
||||
final RocketComponent component = this.root;
|
||||
@ -346,10 +343,7 @@ public class MassCalculation {
|
||||
* MOI Calculation needs to be a two-step process:
|
||||
* (1) calculate overall Center-of-Mass (CM) first (down inline with data-gathering)
|
||||
* (2) Move MOIs to CM via parallel axis theorem (this method)
|
||||
*
|
||||
* @param Center-of-Mass where the MOI should be calculated around.
|
||||
* @param inertias a list of component MassData instances to condense into a single MOI
|
||||
*
|
||||
*
|
||||
* @return freshly calculated Moment-of-Inertia matrix
|
||||
*/
|
||||
/* package-scope */ RigidBody calculateMomentOfInertia() {
|
||||
|
@ -3,7 +3,6 @@ package net.sf.openrocket.masscalc;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import net.sf.openrocket.masscalc.MassCalculation.Type;
|
||||
import net.sf.openrocket.motor.Motor;
|
||||
import net.sf.openrocket.rocketcomponent.FlightConfiguration;
|
||||
import net.sf.openrocket.rocketcomponent.RocketComponent;
|
||||
@ -25,7 +24,7 @@ public class MassCalculator implements Monitorable {
|
||||
// private MassData rocketSpentMassCache;
|
||||
// private MassData propellantMassCache;
|
||||
|
||||
private int modId=0;
|
||||
private int modId = 0;
|
||||
|
||||
////////////////// Constructors ///////////////////
|
||||
public MassCalculator() {
|
||||
@ -40,7 +39,7 @@ public class MassCalculator implements Monitorable {
|
||||
* - includes motors
|
||||
* - for Black Powder & Composite motors, this generally *excludes* propellant
|
||||
*
|
||||
* @param configuration the rocket configuration to calculate for
|
||||
* @param config the rocket configuration to calculate for
|
||||
* @return the MassData struct of the motors at burnout
|
||||
*/
|
||||
public static RigidBody calculateStructure( final FlightConfiguration config) {
|
||||
@ -53,7 +52,7 @@ public class MassCalculator implements Monitorable {
|
||||
* - includes motors
|
||||
* - for Black Powder & Composite motors, this generally *excludes* propellant
|
||||
*
|
||||
* @param configuration the rocket configuration to calculate for
|
||||
* @param config the rocket configuration to calculate for
|
||||
* @return the MassData struct of the motors at burnout
|
||||
*/
|
||||
public static RigidBody calculateBurnout( final FlightConfiguration config) {
|
||||
@ -124,7 +123,6 @@ public class MassCalculator implements Monitorable {
|
||||
* ( or mount-data collides with motor-data )
|
||||
*
|
||||
* @param configuration the rocket configuration
|
||||
* @param type the state of the motors (none, launch mass, burnout mass)
|
||||
* @return a map from each rocket component to its corresponding CG.
|
||||
*/
|
||||
@Deprecated
|
||||
@ -152,10 +150,6 @@ public class MassCalculator implements Monitorable {
|
||||
|
||||
|
||||
////////////////// Mass property calculations ///////////////////
|
||||
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public int getModID() {
|
||||
return this.modId;
|
||||
|
@ -71,6 +71,7 @@ public class EllipticalFinSet extends FinSet {
|
||||
if (MathUtil.equals(this.length, length))
|
||||
return;
|
||||
this.length = length;
|
||||
validateFinTab();
|
||||
fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE);
|
||||
}
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,6 +1,8 @@
|
||||
package net.sf.openrocket.rocketcomponent;
|
||||
|
||||
import java.awt.geom.Line2D;
|
||||
import java.awt.geom.Point2D;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
@ -9,9 +11,8 @@ import org.slf4j.Logger;
|
||||
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.BugException;
|
||||
import net.sf.openrocket.util.Coordinate;
|
||||
|
||||
|
||||
@ -19,10 +20,12 @@ public class FreeformFinSet extends FinSet {
|
||||
private static final Logger log = LoggerFactory.getLogger(FreeformFinSet.class);
|
||||
private static final Translator trans = Application.getTranslator();
|
||||
|
||||
private ArrayList<Coordinate> points = new ArrayList<Coordinate>();
|
||||
public static final double MIN_ROOT_CHORD=0.01; // enforce this to prevent erroneous 'intersection' exceptions.
|
||||
|
||||
private List<Coordinate> points = new ArrayList<>();
|
||||
|
||||
public FreeformFinSet() {
|
||||
points.add(Coordinate.NUL);
|
||||
points.add(Coordinate.ZERO);
|
||||
points.add(new Coordinate(0.025, 0.05));
|
||||
points.add(new Coordinate(0.075, 0.05));
|
||||
points.add(new Coordinate(0.05, 0));
|
||||
@ -30,7 +33,7 @@ public class FreeformFinSet extends FinSet {
|
||||
this.length = 0.05;
|
||||
}
|
||||
|
||||
public FreeformFinSet(Coordinate[] finpoints) throws IllegalFinPointException {
|
||||
public FreeformFinSet(Coordinate[] finpoints) {
|
||||
setPoints(finpoints);
|
||||
}
|
||||
|
||||
@ -45,11 +48,10 @@ public class FreeformFinSet extends FinSet {
|
||||
* @return the new freeform fin set.
|
||||
*/
|
||||
public static FreeformFinSet convertFinSet(FinSet finset) {
|
||||
log.info("Converting " + finset.getComponentName() + " into freeform fin set");
|
||||
final RocketComponent root = finset.getRoot();
|
||||
FreeformFinSet freeform;
|
||||
List<RocketComponent> toInvalidate = Collections.emptyList();
|
||||
|
||||
|
||||
try {
|
||||
if (root instanceof Rocket) {
|
||||
((Rocket) root).freeze();
|
||||
@ -64,21 +66,15 @@ public class FreeformFinSet extends FinSet {
|
||||
} else {
|
||||
position = -1;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Create the freeform fin set
|
||||
Coordinate[] finpoints = finset.getFinPoints();
|
||||
try {
|
||||
freeform = new FreeformFinSet(finpoints);
|
||||
} catch (IllegalFinPointException e) {
|
||||
throw new BugException("Illegal fin points when converting existing fin to " +
|
||||
"freeform fin, fin=" + finset + " points=" + Arrays.toString(finpoints),
|
||||
e);
|
||||
}
|
||||
|
||||
freeform = new FreeformFinSet(finpoints);
|
||||
freeform.setAxialOffset(finset.getAxialMethod(), finset.getAxialOffset());
|
||||
|
||||
// Copy component attributes
|
||||
toInvalidate = freeform.copyFrom(finset);
|
||||
|
||||
|
||||
// Set name
|
||||
final String componentTypeName = finset.getComponentName();
|
||||
final String name = freeform.getName();
|
||||
@ -87,9 +83,9 @@ public class FreeformFinSet extends FinSet {
|
||||
freeform.setName(freeform.getComponentName() +
|
||||
name.substring(componentTypeName.length()));
|
||||
}
|
||||
|
||||
|
||||
freeform.setAppearance(finset.getAppearance());
|
||||
|
||||
|
||||
// Add freeform fin set to parent
|
||||
if (parent != null) {
|
||||
parent.addChild(freeform, position);
|
||||
@ -107,8 +103,6 @@ public class FreeformFinSet extends FinSet {
|
||||
return freeform;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Add a fin point between indices <code>index-1</code> and <code>index</code>.
|
||||
* The point is placed at the midpoint of the current segment.
|
||||
@ -123,8 +117,7 @@ public class FreeformFinSet extends FinSet {
|
||||
// adding a point within the segment affects neither mass nor aerodynamics
|
||||
fireComponentChangeEvent(ComponentChangeEvent.NONFUNCTIONAL_CHANGE);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Remove the fin point with the given index. The first and last fin points
|
||||
* cannot be removed, and will cause an <code>IllegalFinPointException</code>
|
||||
@ -138,144 +131,176 @@ public class FreeformFinSet extends FinSet {
|
||||
throw new IllegalFinPointException("cannot remove first or last point");
|
||||
}
|
||||
|
||||
ArrayList<Coordinate> copy = this.points.clone();
|
||||
copy.remove(index);
|
||||
validate(copy);
|
||||
this.points = copy;
|
||||
// copy the old list in case the operation fails
|
||||
List<Coordinate> copy = new ArrayList<>(this.points);
|
||||
|
||||
fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE);
|
||||
this.points.remove(index);
|
||||
if( ! validate()){
|
||||
// if error, rollback.
|
||||
this.points = copy;
|
||||
}
|
||||
|
||||
fireComponentChangeEvent(ComponentChangeEvent.AEROMASS_CHANGE);
|
||||
}
|
||||
|
||||
|
||||
public int getPointCount() {
|
||||
return points.size();
|
||||
}
|
||||
|
||||
public void setPoints(Coordinate[] points) throws IllegalFinPointException {
|
||||
setPoints(Arrays.asList(points));
|
||||
|
||||
/**
|
||||
* The first point is assumed to be at the origin. If it isn't, it will be moved there.
|
||||
*
|
||||
* @param newPoints new fin points ; replaces previous fin points
|
||||
*/
|
||||
public void setPoints(Coordinate[] newPoints) {
|
||||
if( ! Coordinate.ZERO.equals(newPoints[0])) {
|
||||
final Coordinate p0 = newPoints[0];
|
||||
newPoints = translatePoints(newPoints, p0.x, p0.y);
|
||||
}
|
||||
|
||||
ArrayList<Coordinate> newList = new ArrayList<>(Arrays.asList( newPoints));
|
||||
setPoints( newList );
|
||||
}
|
||||
|
||||
public void setPoints(List<Coordinate> points) throws IllegalFinPointException {
|
||||
ArrayList<Coordinate> list = new ArrayList<Coordinate>(points);
|
||||
validate(list);
|
||||
this.points = list;
|
||||
|
||||
/**
|
||||
* The first point is assumed to be at the origin. If it isn't, it will be moved there.
|
||||
*
|
||||
* @param newPoints New points to set as the exposed edges of the fin
|
||||
*/
|
||||
public void setPoints( List<Coordinate> newPoints) {
|
||||
// copy the old points, in case validation fails
|
||||
List<Coordinate> copy = new ArrayList<>(this.points);
|
||||
|
||||
this.points = newPoints;
|
||||
update();
|
||||
|
||||
if( ! validate()){
|
||||
// on error, reset to the old points
|
||||
this.points = copy;
|
||||
}
|
||||
|
||||
this.length = points.get(points.size() - 1).x;
|
||||
fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE);
|
||||
fireComponentChangeEvent(ComponentChangeEvent.AEROMASS_CHANGE);
|
||||
}
|
||||
|
||||
private double y_body( final double x){
|
||||
return y_body( x, 0.0 );
|
||||
}
|
||||
|
||||
private double y_body( final double x_target, final double x_ref){
|
||||
final SymmetricComponent sym = (SymmetricComponent)getParent();
|
||||
return ( sym.getRadius(x_target) - sym.getRadius( x_ref));
|
||||
}
|
||||
|
||||
public void setPointRelToFin( final int index, final double x_request_fin, final double y_request_fin) throws IllegalFinPointException {
|
||||
final double x_finStart_body = getAxialFront(); // x @ fin start, body frame
|
||||
final double y_finStart_body = y_body( x_finStart_body);
|
||||
|
||||
setPoint( index, x_request_fin + x_finStart_body , y_request_fin + y_finStart_body);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the point at position <code>i</code> to coordinates (x,y).
|
||||
* <p>
|
||||
* Note that this method enforces basic fin shape restrictions (non-negative y,
|
||||
* first and last point locations) silently, but throws an
|
||||
* <code>IllegalFinPointException</code> if the point causes fin segments to
|
||||
* intersect.
|
||||
* <p>
|
||||
* Note that this method silently enforces basic fin shape restrictions
|
||||
* - points may not be within the parent body.
|
||||
* - first point occurs before last (and vice versa)
|
||||
* - first and last points must be on the parent body
|
||||
* - non-self-intersecting fin shape (aborts set on invalid fin point)
|
||||
* </p><p>
|
||||
* NOTE: the fin-point axes differ from rocket axes:
|
||||
* +x within the fin points foreward; +x for the rocket points aft
|
||||
* </p><p>
|
||||
* Moving of the first point in the X-axis is allowed, but this actually moves
|
||||
* all of the other points the corresponding distance back.
|
||||
* all of the other points the corresponding distance back, relative to the first.
|
||||
* That is, moving the first point should not change how the rest of the
|
||||
* points are positioned *relative to the fin-mount*.
|
||||
*
|
||||
* @param index the point index to modify.
|
||||
* @param x the x-coordinate.
|
||||
* @param y the y-coordinate.
|
||||
* @throws IllegalFinPointException if the specified fin point would cause intersecting
|
||||
* segments
|
||||
*/
|
||||
public void setPoint(int index, double x, double y) throws IllegalFinPointException {
|
||||
if (y < 0)
|
||||
y = 0;
|
||||
|
||||
double x0, y0, x1, y1;
|
||||
|
||||
if (index == 0) {
|
||||
|
||||
// Restrict point
|
||||
x = Math.min(x, points.get(points.size() - 1).x);
|
||||
y = 0;
|
||||
x0 = Double.NaN;
|
||||
y0 = Double.NaN;
|
||||
x1 = points.get(1).x;
|
||||
y1 = points.get(1).y;
|
||||
// } else if ( (0 > index) || (points.size() <= index) ){
|
||||
// throw new IllegalFinPointException("Point Index not available!");
|
||||
} else if (index == points.size() - 1) {
|
||||
|
||||
// Restrict point
|
||||
x = Math.max(x, 0);
|
||||
y = 0;
|
||||
x0 = points.get(index - 1).x;
|
||||
y0 = points.get(index - 1).y;
|
||||
x1 = Double.NaN;
|
||||
y1 = Double.NaN;
|
||||
|
||||
} else {
|
||||
|
||||
x0 = points.get(index - 1).x;
|
||||
y0 = points.get(index - 1).y;
|
||||
x1 = points.get(index + 1).x;
|
||||
y1 = points.get(index + 1).y;
|
||||
|
||||
* @param xRequest the x-coordinate.
|
||||
* @param yRequest the y-coordinate.
|
||||
*/
|
||||
public void setPoint( final int index, final double xRequest, final double yRequest) {
|
||||
final SymmetricComponent body = (SymmetricComponent)getParent();
|
||||
|
||||
final int lastPointIndex = this.points.size() - 1;
|
||||
final double xFinEnd = points.get(lastPointIndex).x;
|
||||
final double xFinStart = getAxialFront(); // x of fin start, body-frame
|
||||
final double yFinStart = body.getRadius( xFinStart); // y of fin start, body-frame
|
||||
final double xBodyStart = -xFinStart; // x-offset from fin to body; fin-frame
|
||||
|
||||
// initial guess at these values; further checks follow.
|
||||
double xAgreed = xRequest;
|
||||
double yAgreed = yRequest;
|
||||
|
||||
// clamp x coordinates:
|
||||
// within bounds, and consistent with the rest of the fin (at this time).
|
||||
if( 0 == index ) {
|
||||
// restrict the first point to be between the parent's start, and the last fin point
|
||||
xAgreed = Math.max( xBodyStart, Math.min( xAgreed, xFinEnd - MIN_ROOT_CHORD ));
|
||||
}else if( lastPointIndex == index ){
|
||||
// restrict the last point to be between the first fin point, and the parent's end length.
|
||||
xAgreed = Math.max( MIN_ROOT_CHORD, Math.min( xAgreed, xBodyStart + body.getLength()));
|
||||
}
|
||||
|
||||
// adjust y-value to be consistent with body
|
||||
final double yBody_atPoint= body.getRadius( xFinStart + xAgreed) - yFinStart;
|
||||
if (index == 0 || index == lastPointIndex) {
|
||||
// for the first and last points: set y-value to *exactly* match parent body:
|
||||
yAgreed = yBody_atPoint;
|
||||
}else{
|
||||
// for all other points, merely insist that the point is outside the body...
|
||||
yAgreed = Math.max( yAgreed, yBody_atPoint);
|
||||
}
|
||||
|
||||
// if moving either begin or end points, we'll probably have to update the position, as well.
|
||||
final AxialMethod locationMethod = getAxialMethod();
|
||||
final double priorXOffset = getAxialOffset();
|
||||
|
||||
// Check for intersecting
|
||||
double px0, py0, px1, py1;
|
||||
px0 = 0;
|
||||
py0 = 0;
|
||||
for (int i = 1; i < points.size(); i++) {
|
||||
px1 = points.get(i).x;
|
||||
py1 = points.get(i).y;
|
||||
if( 0 == index){
|
||||
movePoints( xAgreed);
|
||||
this.length = points.get( lastPointIndex ).x;
|
||||
|
||||
if (i != index - 1 && i != index && i != index + 1) {
|
||||
if (intersects(x0, y0, x, y, px0, py0, px1, py1)) {
|
||||
throw new IllegalFinPointException("segments intersect");
|
||||
}
|
||||
}
|
||||
if (i != index && i != index + 1 && i != index + 2) {
|
||||
if (intersects(x, y, x1, y1, px0, py0, px1, py1)) {
|
||||
throw new IllegalFinPointException("segments intersect");
|
||||
}
|
||||
if( AxialMethod.TOP == locationMethod){
|
||||
setAxialOffset( AxialMethod.TOP, priorXOffset + xAgreed );
|
||||
}else if(AxialMethod.MIDDLE == locationMethod){
|
||||
setAxialOffset( AxialMethod.MIDDLE, priorXOffset + xAgreed/2 );
|
||||
}
|
||||
}else if( lastPointIndex == index ){
|
||||
points.set(index, new Coordinate( xAgreed, yAgreed ));
|
||||
this.length = xAgreed;
|
||||
|
||||
px0 = px1;
|
||||
py0 = py1;
|
||||
if( AxialMethod.MIDDLE == locationMethod){
|
||||
setAxialOffset( AxialMethod.MIDDLE, priorXOffset + (xAgreed - xFinEnd)/2 );
|
||||
}else if(AxialMethod.BOTTOM== locationMethod){
|
||||
setAxialOffset( AxialMethod.BOTTOM, priorXOffset + (xAgreed - xFinEnd) );
|
||||
}
|
||||
}else{
|
||||
points.set(index, new Coordinate( xAgreed, yAgreed ));
|
||||
}
|
||||
|
||||
if (index == 0) {
|
||||
|
||||
//System.out.println("Set point zero to x:" + x);
|
||||
for (int i = 1; i < points.size(); i++) {
|
||||
Coordinate c = points.get(i);
|
||||
points.set(i, c.setX(c.x - x));
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
points.set(index, new Coordinate(x, y));
|
||||
|
||||
// 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)){
|
||||
// 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));
|
||||
return;
|
||||
}
|
||||
if (index == 0 || index == points.size() - 1) {
|
||||
this.length = points.get(points.size() - 1).x;
|
||||
}
|
||||
fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE);
|
||||
|
||||
fireComponentChangeEvent(ComponentChangeEvent.AEROMASS_CHANGE);
|
||||
}
|
||||
|
||||
|
||||
|
||||
private boolean intersects(double ax0, double ay0, double ax1, double ay1,
|
||||
double bx0, double by0, double bx1, double by1) {
|
||||
|
||||
double d = ((by1 - by0) * (ax1 - ax0) - (bx1 - bx0) * (ay1 - ay0));
|
||||
|
||||
double ua = ((bx1 - bx0) * (ay0 - by0) - (by1 - by0) * (ax0 - bx0)) / d;
|
||||
double ub = ((ax1 - ax0) * (ay0 - by0) - (ay1 - ay0) * (ax0 - bx0)) / d;
|
||||
|
||||
return (ua >= 0) && (ua <= 1) && (ub >= 0) && (ub <= 1);
|
||||
|
||||
private void movePoints( final double delta_x){
|
||||
// skip 0th index -- it's the local origin and is always (0,0)
|
||||
for( int index=1; index < points.size(); ++index){
|
||||
final Coordinate oldPoint = this.points.get( index);
|
||||
final Coordinate newPoint = oldPoint.sub( delta_x, 0.0f, 0.0f);
|
||||
points.set( index, newPoint);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Coordinate[] getFinPoints() {
|
||||
return points.toArray(new Coordinate[0]);
|
||||
@ -297,31 +322,206 @@ public class FreeformFinSet extends FinSet {
|
||||
return trans.get("FreeformFinSet.FreeformFinSet");
|
||||
}
|
||||
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
protected RocketComponent copyWithOriginalID() {
|
||||
RocketComponent c = super.copyWithOriginalID();
|
||||
((FreeformFinSet) c).points = this.points.clone();
|
||||
|
||||
((FreeformFinSet) c).points = new ArrayList<>(this.points);
|
||||
|
||||
return c;
|
||||
}
|
||||
|
||||
private void validate(ArrayList<Coordinate> pts) throws IllegalFinPointException {
|
||||
final int n = pts.size();
|
||||
if (pts.get(0).x != 0 || pts.get(0).y != 0 ||
|
||||
pts.get(n - 1).x < 0 || pts.get(n - 1).y != 0) {
|
||||
throw new IllegalFinPointException("Start or end point illegal.");
|
||||
}
|
||||
for (int i = 0; i < n - 1; i++) {
|
||||
for (int j = i + 2; j < n - 1; j++) {
|
||||
if (intersects(pts.get(i).x, pts.get(i).y, pts.get(i + 1).x, pts.get(i + 1).y,
|
||||
pts.get(j).x, pts.get(j).y, pts.get(j + 1).x, pts.get(j + 1).y)) {
|
||||
throw new IllegalFinPointException("segments intersect");
|
||||
}
|
||||
@Override
|
||||
public void setAxialOffset( final AxialMethod newAxialMethod, final double newOffsetRequest){
|
||||
super.setAxialOffset( newAxialMethod, newOffsetRequest);
|
||||
|
||||
if( null != parent ) {
|
||||
// if the new position would cause fin overhang, only allow movement up to the end of the parent component.
|
||||
// N.B. if you want a fin to overhang, add & adjust interior points.
|
||||
final double backOverhang = getAxialOffset(AxialMethod.BOTTOM);
|
||||
if (0 < backOverhang) {
|
||||
final double newOffset = newOffsetRequest - backOverhang;
|
||||
super.setAxialOffset(newAxialMethod, newOffset);
|
||||
}
|
||||
if (pts.get(i).z != 0) {
|
||||
throw new IllegalFinPointException("z-coordinate not zero");
|
||||
final double frontOverhang = getAxialFront();
|
||||
if (0 > frontOverhang) {
|
||||
final double newOffset = newOffsetRequest - frontOverhang;
|
||||
super.setAxialOffset(newAxialMethod, newOffset);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(){
|
||||
final int lastPointIndex = this.points.size() - 1;
|
||||
this.length = points.get(lastPointIndex).x;
|
||||
|
||||
this.setAxialOffset( this.axialMethod, this.axialOffset);
|
||||
|
||||
clampFirstPoint();
|
||||
clampInteriorPoints();
|
||||
clampLastPoint();
|
||||
|
||||
validateFinTab();
|
||||
}
|
||||
|
||||
// if we translate the points, correct the first point, because it may be inconsistent
|
||||
private void clampFirstPoint(){
|
||||
double xFinStart = getAxialFront(); // x @ fin start, body frame
|
||||
final double xFinOffset = getAxialOffset();
|
||||
if( 0 > xFinStart ){
|
||||
setAxialOffset( xFinOffset - xFinStart);
|
||||
}
|
||||
}
|
||||
|
||||
private void clampInteriorPoints(){
|
||||
if( null == this.parent ){
|
||||
// this is bad, but seems to only occur during unit tests.
|
||||
return;
|
||||
}
|
||||
final SymmetricComponent symmetricParent = (SymmetricComponent)this.getParent();
|
||||
|
||||
final Coordinate finFront = getFinFront();
|
||||
|
||||
// omit end points index
|
||||
for( int index=1; index < (points.size()-1); ++index){
|
||||
final Coordinate oldPoint = this.points.get( index);
|
||||
|
||||
final double yBody = symmetricParent.getRadius( oldPoint.x + finFront.x);
|
||||
final double yFinPoint = finFront.y+ oldPoint.y;
|
||||
|
||||
if( yBody > yFinPoint ){
|
||||
final Coordinate newPoint = oldPoint.setY( yBody - finFront.y );
|
||||
points.set( index, newPoint);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if we translate the points, the final point may become inconsistent
|
||||
private void clampLastPoint(){
|
||||
if( null == this.parent ){
|
||||
// this is bad, but seems to only occur during unit tests.
|
||||
return;
|
||||
}
|
||||
|
||||
final SymmetricComponent body = (SymmetricComponent)getParent();
|
||||
// clamp the final x coord to the end of the parent body.
|
||||
final int lastPointIndex = points.size() - 1;
|
||||
final Coordinate oldPoint = points.get( lastPointIndex);
|
||||
|
||||
final double xFinStart_body = getAxialFront(); // x @ fin start, body frame
|
||||
final double xBodyEnd_fin = body.getLength() - xFinStart_body;
|
||||
|
||||
double x_clamped = Math.min( oldPoint.x, xBodyEnd_fin);
|
||||
double y_clamped = body.getRadius( x_clamped+xFinStart_body) - body.getRadius( xFinStart_body);
|
||||
|
||||
|
||||
points.set( lastPointIndex, new Coordinate( x_clamped, y_clamped, 0));
|
||||
}
|
||||
|
||||
private boolean validate() {
|
||||
final Coordinate firstPoint = this.points.get(0);
|
||||
if (firstPoint.x != 0 || firstPoint.y != 0 ){
|
||||
log.error("Start point illegal -- not located at (0,0): "+firstPoint+ " ("+ getName()+")");
|
||||
return false;
|
||||
}
|
||||
|
||||
final Coordinate lastPoint = this.points.get( points.size() -1);
|
||||
if( lastPoint.x < 0){
|
||||
log.error("End point illegal: end point starts in front of start point: "+lastPoint.x);
|
||||
return false;
|
||||
}
|
||||
|
||||
// the last point *is* restricted to be on the surface of its owning component:
|
||||
SymmetricComponent symBody = (SymmetricComponent)this.getParent();
|
||||
if( null != symBody ){
|
||||
final double startOffset = this.getAxialFront();
|
||||
final Coordinate finStart = new Coordinate( startOffset, symBody.getRadius(startOffset) );
|
||||
|
||||
// campare x-values
|
||||
final Coordinate finAtLast = lastPoint.add(finStart);
|
||||
if( symBody.getLength() < finAtLast.x ){
|
||||
log.error("End point falls after parent body ends: ["+symBody.getName()+"]. Exception: ", new IllegalFinPointException("Fin ends after its parent body \""+symBody.getName()+"\". Ignoring."));
|
||||
log.error(String.format(" ..fin position: (x: %12.10f via: %s)", this.axialOffset, this.axialMethod.name()));
|
||||
log.error(String.format(" ..Body Length: %12.10f finLength: %12.10f", symBody.getLength(), this.getLength()));
|
||||
log.error(String.format(" ..fin endpoint: (x: %12.10f, y: %12.10f)", finAtLast.x, finAtLast.y));
|
||||
return false;
|
||||
}
|
||||
|
||||
// compare the y-values
|
||||
final Coordinate bodyAtLast = finAtLast.setY( symBody.getRadius( finAtLast.x ) );
|
||||
if( 0.0001 < Math.abs( finAtLast.y - bodyAtLast.y) ){
|
||||
String numbers = String.format( "finStart=(%6.2g,%6.2g) // fin_end=(%6.2g,%6.2g) // body=(%6.2g,%6.2g)", finStart.x, finStart.y, finAtLast.x, finAtLast.y, bodyAtLast.x, bodyAtLast.y );
|
||||
log.error("End point does not touch its parent body ["+symBody.getName()+"]. exception: ", new IllegalFinPointException("End point does not touch its parent body! Expected: "+numbers));
|
||||
log.error(" .."+numbers);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if( intersects()){
|
||||
log.error("found intersection in finset points!");
|
||||
return false;
|
||||
}
|
||||
|
||||
final int lastIndex = points.size() - 1;
|
||||
final List<Coordinate> pts = this.points;
|
||||
for (int i = 0; i < lastIndex; i++) {
|
||||
if (pts.get(i).z != 0) {
|
||||
log.error("z-coordinate not zero");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if *any* of the fin-point line segments intersects with another.
|
||||
*
|
||||
* @return true if an intersection is found
|
||||
*/
|
||||
public boolean intersects( ){
|
||||
for( int index=0; index < (this.points.size()-1); ++index ){
|
||||
if( intersects( index )){
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the line segment from targetIndex to targetIndex+1 intersects with any other part of the fin.
|
||||
*
|
||||
*
|
||||
*
|
||||
* @return true if an intersection was found
|
||||
*/
|
||||
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());
|
||||
}
|
||||
|
||||
// (pre-check the indices above.)
|
||||
Point2D.Double p1 = new Point2D.Double( points.get(targetIndex).x, points.get(targetIndex).y);
|
||||
Point2D.Double p2 = new Point2D.Double( points.get(targetIndex+1).x, points.get(targetIndex+1).y);
|
||||
Line2D.Double targetLine = new Line2D.Double( p1, p2);
|
||||
|
||||
for (int comparisonIndex = 0; comparisonIndex < (points.size()-1); ++comparisonIndex ) {
|
||||
if( 2 > Math.abs( targetIndex - comparisonIndex) ){
|
||||
// a line segment will trivially not intersect with itself
|
||||
// nor can adjacent line segments intersect with each other, because they share a common endpoint.
|
||||
continue;
|
||||
}
|
||||
|
||||
Line2D.Double comparisonLine = new Line2D.Double( points.get(comparisonIndex).x, points.get(comparisonIndex).y, // p1
|
||||
points.get(comparisonIndex+1).x, points.get(comparisonIndex+1).y); // p2
|
||||
|
||||
if ( targetLine.intersectsLine( comparisonLine ) ) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -910,8 +910,8 @@ public abstract class RocketComponent implements ChangeSource, Cloneable, Iterab
|
||||
|
||||
/**
|
||||
* Get the positioning of the component relative to its parent component.
|
||||
* This is one of the enums of {@link AxialMethod}. A setter method is not provided,
|
||||
* but can be provided by a subclass.
|
||||
*
|
||||
* @return This will return one of the enums of {@link AxialMethod}
|
||||
*/
|
||||
public final AxialMethod getAxialMethod() {
|
||||
return axialMethod;
|
||||
@ -952,7 +952,7 @@ public abstract class RocketComponent implements ChangeSource, Cloneable, Iterab
|
||||
public double getAxialOffset(AxialMethod asMethod) {
|
||||
double parentLength = 0;
|
||||
if (null != this.parent) {
|
||||
parentLength = this.parent.length;
|
||||
parentLength = this.parent.length;
|
||||
}
|
||||
|
||||
if(AxialMethod.ABSOLUTE == asMethod){
|
||||
@ -965,7 +965,11 @@ public abstract class RocketComponent implements ChangeSource, Cloneable, Iterab
|
||||
public double getAxialOffset() {
|
||||
return this.axialOffset;
|
||||
}
|
||||
|
||||
|
||||
public double getAxialFront(){
|
||||
return this.position.x;
|
||||
}
|
||||
|
||||
public double getRadiusOffset() {
|
||||
mutex.verify();
|
||||
return 0;
|
||||
@ -1017,14 +1021,14 @@ public abstract class RocketComponent implements ChangeSource, Cloneable, Iterab
|
||||
* Set the position value of the component. The exact meaning of the value
|
||||
* depends on the current relative positioning.
|
||||
*
|
||||
* @param newOffset the position value of the component.
|
||||
*/
|
||||
public void setAxialOffset(double newOffset) {
|
||||
* @param newOffset the desired offset of this component, using the components current axial-method
|
||||
*/
|
||||
public void setAxialOffset(double newOffset) {
|
||||
this.setAxialOffset(this.axialMethod, newOffset);
|
||||
this.fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE);
|
||||
}
|
||||
|
||||
final protected void setAxialOffset( final AxialMethod requestedMethod, final double requestedOffset) {
|
||||
protected void setAxialOffset( final AxialMethod requestedMethod, final double requestedOffset) {
|
||||
checkState();
|
||||
|
||||
double newX = Double.NaN;
|
||||
@ -1060,7 +1064,7 @@ public abstract class RocketComponent implements ChangeSource, Cloneable, Iterab
|
||||
this.setAxialOffset(this.axialMethod, this.axialOffset);
|
||||
}
|
||||
|
||||
public final void updateChildren(){
|
||||
private final void updateChildren(){
|
||||
this.update();
|
||||
for( RocketComponent rc : children ){
|
||||
rc.updateChildren();
|
||||
@ -1079,7 +1083,7 @@ public abstract class RocketComponent implements ChangeSource, Cloneable, Iterab
|
||||
* <p>
|
||||
* NOTE: the length of this array returned always equals this.getInstanceCount()
|
||||
*
|
||||
* @return an generated (i.e. new) array of instance locations
|
||||
* @return a generated (i.e. new) array of instance locations
|
||||
*/
|
||||
// @Override Me !
|
||||
public Coordinate[] getInstanceLocations(){
|
||||
@ -1529,8 +1533,9 @@ public abstract class RocketComponent implements ChangeSource, Cloneable, Iterab
|
||||
public final RocketComponent getRoot() {
|
||||
checkState();
|
||||
RocketComponent gp = this;
|
||||
while (gp.parent != null)
|
||||
while (gp.parent != null){
|
||||
gp = gp.parent;
|
||||
}
|
||||
return gp;
|
||||
}
|
||||
|
||||
@ -2119,12 +2124,21 @@ public abstract class RocketComponent implements ChangeSource, Cloneable, Iterab
|
||||
// multi-line output
|
||||
protected StringBuilder toDebugDetail() {
|
||||
StringBuilder buf = new StringBuilder();
|
||||
|
||||
// infer the calling method name
|
||||
StackTraceElement[] stackTrace = (new Exception()).getStackTrace();
|
||||
buf.append(" >> Dumping Detailed Information from: " + stackTrace[1].getMethodName() + "\n");
|
||||
buf.append(" current Component: " + this.getName() + " ofClass: " + this.getClass().getSimpleName() + "\n");
|
||||
buf.append(" offset: " + this.axialOffset + " via: " + this.axialMethod.name() + " => " + this.getAxialOffset() + "\n");
|
||||
buf.append(" thisCenterX: " + this.position.x + "\n");
|
||||
buf.append(" this length: " + this.length + "\n");
|
||||
String callingMethod = stackTrace[1].getMethodName();
|
||||
for( StackTraceElement el : stackTrace ){
|
||||
if( ! "toDebugDetail".equals(el.getMethodName())){
|
||||
callingMethod = el.getMethodName();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
buf.append(String.format(" >> Dumping Detailed Information from: %s\n", callingMethod));
|
||||
buf.append(String.format(" At Component: %s, of class: %s \n", this.getName(), this.getClass().getSimpleName()));
|
||||
buf.append(String.format(" position: %.6f at offset: %.4f via: %s\n", this.position.x, this.axialOffset, this.axialMethod.name()));
|
||||
buf.append(String.format(" length: %.4f\n", this.length ));
|
||||
return buf;
|
||||
}
|
||||
|
||||
@ -2152,7 +2166,24 @@ public abstract class RocketComponent implements ChangeSource, Cloneable, Iterab
|
||||
public void toDebugTreeNode(final StringBuilder buffer, final String indent) {
|
||||
String prefix = String.format("%s%s (x%d)", indent, this.getName(), this.getInstanceCount() );
|
||||
|
||||
buffer.append(String.format("%-40s| %6.4f; %24s; %24s; \n", prefix, getLength(), getPosition().toPreciseString(), getComponentLocations()[0].toPreciseString() ));
|
||||
// 1) instanced vs non-instanced
|
||||
if( 1 == getInstanceCount() ){
|
||||
// un-instanced RocketComponents (usual case)
|
||||
buffer.append(String.format("%-40s| %5.3f; %24s; %24s; ", prefix, this.getLength(), this.axialOffset, this.getComponentLocations()[0]));
|
||||
buffer.append(String.format("(offset: %4.1f via: %s )\n", this.getAxialOffset(), this.axialMethod.name()));
|
||||
}else if( this instanceof Instanceable ){
|
||||
// instanced components -- think motor clusters or booster stage clusters
|
||||
final String patternName = ((Instanceable)this).getPatternName();
|
||||
buffer.append(String.format("%-40s (cluster: %s )", prefix, patternName));
|
||||
buffer.append(String.format("(offset: %4.1f via: %s )\n", this.getAxialOffset(), this.axialMethod.name()));
|
||||
|
||||
for (int instanceNumber = 0; instanceNumber < this.getInstanceCount(); instanceNumber++) {
|
||||
final String instancePrefix = String.format("%s [%2d/%2d]", indent, instanceNumber+1, getInstanceCount());
|
||||
buffer.append(String.format("%-40s| %5.3f; %24s; %24s;\n", instancePrefix, getLength(), this.axialOffset, getLocations()[0]));
|
||||
}
|
||||
}else{
|
||||
throw new IllegalStateException("This is a developer error! If you implement an instanced class, please subclass the Instanceable interface.");
|
||||
}
|
||||
|
||||
// 2) if this is an ACTING motor mount:
|
||||
if(( this instanceof MotorMount ) &&( ((MotorMount)this).isMotorMount())){
|
||||
|
@ -1,5 +1,11 @@
|
||||
package net.sf.openrocket.rocketcomponent;
|
||||
|
||||
import static java.lang.Math.sin;
|
||||
import static net.sf.openrocket.util.MathUtil.pow2;
|
||||
import static net.sf.openrocket.util.MathUtil.pow3;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import net.sf.openrocket.l10n.Translator;
|
||||
import net.sf.openrocket.preset.ComponentPreset;
|
||||
import net.sf.openrocket.preset.ComponentPreset.Type;
|
||||
@ -7,12 +13,6 @@ import net.sf.openrocket.startup.Application;
|
||||
import net.sf.openrocket.util.Coordinate;
|
||||
import net.sf.openrocket.util.MathUtil;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import static java.lang.Math.sin;
|
||||
import static net.sf.openrocket.util.MathUtil.pow2;
|
||||
import static net.sf.openrocket.util.MathUtil.pow3;
|
||||
|
||||
|
||||
public class Transition extends SymmetricComponent {
|
||||
private static final Translator trans = Application.getTranslator();
|
||||
@ -526,13 +526,16 @@ public class Transition extends SymmetricComponent {
|
||||
* Check whether the given type can be added to this component. Transitions allow any
|
||||
* InternalComponents to be added.
|
||||
*
|
||||
* @param ctype The RocketComponent class type to add.
|
||||
* @param comptype The RocketComponent class type to add.
|
||||
* @return Whether such a component can be added.
|
||||
*/
|
||||
@Override
|
||||
public boolean isCompatible(Class<? extends RocketComponent> ctype) {
|
||||
if (InternalComponent.class.isAssignableFrom(ctype))
|
||||
public boolean isCompatible(Class<? extends RocketComponent> comptype) {
|
||||
if (InternalComponent.class.isAssignableFrom(comptype)){
|
||||
return true;
|
||||
}else if ( FreeformFinSet.class.isAssignableFrom(comptype)){
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -932,4 +935,5 @@ public class Transition extends SymmetricComponent {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,13 +1,13 @@
|
||||
package net.sf.openrocket.rocketcomponent;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import net.sf.openrocket.l10n.Translator;
|
||||
import net.sf.openrocket.startup.Application;
|
||||
import net.sf.openrocket.util.Coordinate;
|
||||
import net.sf.openrocket.util.MathUtil;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* A set of trapezoidal fins. The root and tip chords are perpendicular to the rocket
|
||||
* base line, while the leading and aft edges may be slanted.
|
||||
@ -65,6 +65,7 @@ public class TrapezoidFinSet extends FinSet {
|
||||
this.sweep = sweep;
|
||||
this.height = height;
|
||||
this.thickness = thickness;
|
||||
|
||||
fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE);
|
||||
}
|
||||
|
||||
@ -76,6 +77,7 @@ public class TrapezoidFinSet extends FinSet {
|
||||
if (length == r)
|
||||
return;
|
||||
length = Math.max(r, 0);
|
||||
validateFinTab();
|
||||
fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE);
|
||||
}
|
||||
|
||||
|
@ -268,14 +268,9 @@ public class TubeFinSet extends ExternalComponent {
|
||||
}
|
||||
|
||||
// translate each to the center of mass.
|
||||
final double hypot = getOuterRadius() + getBodyRadius();
|
||||
final double finrotation = 2 * Math.PI / fins;
|
||||
double angularoffset = 0.0;
|
||||
double totalInertia = 0.0;
|
||||
for (int i = 0; i < fins; i++) {
|
||||
double offset = hypot * Math.cos(angularoffset);
|
||||
totalInertia += inertia + MathUtil.pow2(offset);
|
||||
angularoffset += finrotation;
|
||||
totalInertia += inertia + MathUtil.pow2( this.axialOffset);
|
||||
}
|
||||
return totalInertia;
|
||||
}
|
||||
|
@ -32,8 +32,9 @@ public enum AxialMethod implements DistanceMethod {
|
||||
@Override
|
||||
public double getAsOffset(double position, double innerLength, double outerLength){
|
||||
return position - outerLength;
|
||||
//return outerLength - position;
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
// measure from the top of the target component to the top of the subject component
|
||||
TOP (Application.getTranslator().get("RocketComponent.Position.Method.Axial.TOP")){
|
||||
@ -61,6 +62,7 @@ public enum AxialMethod implements DistanceMethod {
|
||||
@Override
|
||||
public double getAsPosition(double offset, double innerLength, double outerLength){
|
||||
return offset + (outerLength - innerLength) / 2;
|
||||
// return (outerLength - innerLength) / 2 - offset;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -77,6 +79,7 @@ public enum AxialMethod implements DistanceMethod {
|
||||
@Override
|
||||
public double getAsPosition(double offset, double innerLength, double outerLength){
|
||||
return offset + (outerLength - innerLength);
|
||||
//return outerLength - innerLength - offset;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -54,15 +54,16 @@ public class BasicTumbleStatus extends SimulationStatus {
|
||||
continue;
|
||||
}
|
||||
if (component instanceof FinSet) {
|
||||
final FinSet finComponent = ((FinSet) component);
|
||||
final double finArea = finComponent.getPlanformArea();
|
||||
int finCount = finComponent.getFinCount();
|
||||
|
||||
double finComponent = ((FinSet) component).getFinArea();
|
||||
int finCount = ((FinSet) component).getFinCount();
|
||||
// check bounds on finCount.
|
||||
if (finCount >= finEff.length) {
|
||||
finCount = finEff.length - 1;
|
||||
}
|
||||
|
||||
aFins += finComponent * finEff[finCount];
|
||||
aFins += finArea * finEff[finCount];
|
||||
|
||||
} else if (component instanceof SymmetricComponent) {
|
||||
aBt += ((SymmetricComponent) component).getComponentPlanformArea();
|
||||
|
@ -613,17 +613,14 @@ public class TestRockets {
|
||||
bodytube = new BodyTube(0.69, 0.033, 0.001);
|
||||
|
||||
finset = new FreeformFinSet();
|
||||
try {
|
||||
finset.setPoints(new Coordinate[] {
|
||||
new Coordinate(0, 0),
|
||||
new Coordinate(0.115, 0.072),
|
||||
new Coordinate(0.255, 0.072),
|
||||
new Coordinate(0.255, 0.037),
|
||||
new Coordinate(0.150, 0)
|
||||
});
|
||||
} catch (IllegalFinPointException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
final Coordinate[] finPoints = {
|
||||
new Coordinate(0, 0),
|
||||
new Coordinate(0.115, 0.072),
|
||||
new Coordinate(0.255, 0.072),
|
||||
new Coordinate(0.255, 0.037),
|
||||
new Coordinate(0.150, 0)};
|
||||
finset.setPoints(finPoints);
|
||||
|
||||
finset.setThickness(0.003);
|
||||
finset.setFinCount(4);
|
||||
|
||||
|
@ -3,29 +3,77 @@
|
||||
*/
|
||||
package net.sf.openrocket.file.rocksim.importt;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.io.File;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.HashMap;
|
||||
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
|
||||
import com.google.inject.AbstractModule;
|
||||
import com.google.inject.Guice;
|
||||
import com.google.inject.Injector;
|
||||
import com.google.inject.Module;
|
||||
import com.google.inject.util.Modules;
|
||||
|
||||
import net.sf.openrocket.ServicesForTesting;
|
||||
import net.sf.openrocket.aerodynamics.WarningSet;
|
||||
import net.sf.openrocket.l10n.DebugTranslator;
|
||||
import net.sf.openrocket.l10n.Translator;
|
||||
import net.sf.openrocket.plugin.PluginModule;
|
||||
import net.sf.openrocket.rocketcomponent.BodyTube;
|
||||
import net.sf.openrocket.rocketcomponent.EllipticalFinSet;
|
||||
import net.sf.openrocket.rocketcomponent.FinSet;
|
||||
import net.sf.openrocket.rocketcomponent.TrapezoidFinSet;
|
||||
import net.sf.openrocket.startup.Application;
|
||||
import net.sf.openrocket.util.Coordinate;
|
||||
import net.sf.openrocket.util.MathUtil;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.HashMap;
|
||||
|
||||
/**
|
||||
* FinSetHandler Tester.
|
||||
*
|
||||
*/
|
||||
public class FinSetHandlerTest extends TestCase {
|
||||
public class FinSetHandlerTest {
|
||||
|
||||
final static double EPSILON = MathUtil.EPSILON;
|
||||
|
||||
@BeforeClass
|
||||
public static void setup() {
|
||||
Module applicationModule = new ServicesForTesting();
|
||||
|
||||
Module pluginModule = new PluginModule();
|
||||
|
||||
Module debugTranslator = new AbstractModule() {
|
||||
@Override
|
||||
protected void configure() {
|
||||
bind(Translator.class).toInstance(new DebugTranslator(null));
|
||||
}
|
||||
};
|
||||
|
||||
Injector injector = Guice.createInjector(Modules.override(applicationModule).with(debugTranslator), pluginModule);
|
||||
|
||||
Application.setInjector(injector);
|
||||
|
||||
File tmpDir = new File("./tmp");
|
||||
if (!tmpDir.exists()) {
|
||||
boolean success = tmpDir.mkdirs();
|
||||
assertTrue("Unable to create core/tmp dir needed for tests.", success);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Method: asOpenRocket(WarningSet warnings)
|
||||
*
|
||||
* @throws Exception thrown if something goes awry
|
||||
*/
|
||||
@org.junit.Test
|
||||
@Test
|
||||
public void testAsOpenRocket() throws Exception {
|
||||
|
||||
FinSetHandler dto = new FinSetHandler(null, new BodyTube());
|
||||
@ -37,15 +85,15 @@ public class FinSetHandlerTest extends TestCase {
|
||||
dto.closeElement("ShapeCode", attributes, "0", warnings);
|
||||
dto.closeElement("Xb", attributes, "2", warnings);
|
||||
dto.closeElement("FinCount", attributes, "4", warnings);
|
||||
dto.closeElement("RootChord", attributes, "10", warnings);
|
||||
dto.closeElement("TipChord", attributes, "11", warnings);
|
||||
dto.closeElement("RootChord", attributes, "100", warnings);
|
||||
dto.closeElement("TipChord", attributes, "50", warnings);
|
||||
dto.closeElement("SemiSpan", attributes, "12", warnings);
|
||||
dto.closeElement("MidChordLen", attributes, "13", warnings);
|
||||
dto.closeElement("SweepDistance", attributes, "14", warnings);
|
||||
dto.closeElement("Thickness", attributes, "200", warnings);
|
||||
dto.closeElement("TipShapeCode", attributes, "1", warnings);
|
||||
dto.closeElement("TabLength", attributes, "400", warnings);
|
||||
dto.closeElement("TabDepth", attributes, "500", warnings);
|
||||
dto.closeElement("TabLength", attributes, "40", warnings);
|
||||
dto.closeElement("TabDepth", attributes, "50", warnings);
|
||||
dto.closeElement("TabOffset", attributes, "30", warnings);
|
||||
dto.closeElement("RadialAngle", attributes, ".123", warnings);
|
||||
dto.closeElement("PointList", attributes, "20,0|2,2|0,0", warnings);
|
||||
@ -55,34 +103,37 @@ public class FinSetHandlerTest extends TestCase {
|
||||
FinSet fins = dto.asOpenRocket(set);
|
||||
assertNotNull(fins);
|
||||
assertEquals(0, set.size());
|
||||
|
||||
String debugInfo = fins.toDebugDetail().toString();
|
||||
|
||||
assertEquals("The name", fins.getName());
|
||||
assertTrue(fins instanceof TrapezoidFinSet);
|
||||
assertEquals(4, fins.getFinCount());
|
||||
assertEquals("imported fin count does not match.", 4, fins.getFinCount());
|
||||
|
||||
assertEquals(0.012d, ((TrapezoidFinSet) fins).getHeight());
|
||||
assertEquals(0.012d, fins.getSpan());
|
||||
assertEquals("imported fin height does not match.", 0.012d, ((TrapezoidFinSet) fins).getHeight(), EPSILON);
|
||||
assertEquals("imported fin span does not match.", 0.012d, fins.getSpan(), EPSILON);
|
||||
|
||||
assertEquals(0.2d, fins.getThickness());
|
||||
assertEquals(0.4d, fins.getTabLength());
|
||||
assertEquals(0.5d, fins.getTabHeight());
|
||||
assertEquals(0.03d, fins.getTabShift());
|
||||
assertEquals(.123d, fins.getBaseRotation());
|
||||
assertEquals("imported fin thickness does not match.", 0.2d, fins.getThickness(), EPSILON);
|
||||
assertEquals("imported fin tab length does not match: "+debugInfo, 0.04d, fins.getTabLength(), EPSILON);
|
||||
assertEquals("imported fin tab height does not match: "+debugInfo, 0.05d, fins.getTabHeight(), EPSILON);
|
||||
assertEquals("imported fin shift does not match.", 0.03d, fins.getTabOffset(), EPSILON);
|
||||
assertEquals("imported fin rotation does not match.", .123d, fins.getBaseRotation(), EPSILON);
|
||||
|
||||
dto.closeElement("ShapeCode", attributes, "1", warnings);
|
||||
|
||||
fins = dto.asOpenRocket(set);
|
||||
assertNotNull(fins);
|
||||
assertEquals(0, set.size());
|
||||
|
||||
assertEquals("The name", fins.getName());
|
||||
assertTrue(fins instanceof EllipticalFinSet);
|
||||
assertEquals(4, fins.getFinCount());
|
||||
assertEquals("imported fin count does not match.", 4, fins.getFinCount());
|
||||
|
||||
assertEquals(0.2d, fins.getThickness());
|
||||
assertEquals(0.4d, fins.getTabLength());
|
||||
assertEquals(0.5d, fins.getTabHeight());
|
||||
assertEquals(0.03d, fins.getTabShift());
|
||||
assertEquals(.123d, fins.getBaseRotation());
|
||||
assertEquals("imported fin thickness does not match.", 0.2d, fins.getThickness(), EPSILON);
|
||||
assertEquals("imported fin tab length does not match.", 0.04d, fins.getTabLength(), EPSILON);
|
||||
assertEquals("imported fin tab height does not match.", 0.05d, fins.getTabHeight(), EPSILON);
|
||||
assertEquals("imported fin tab shift does not match.", 0.03d, fins.getTabOffset(), EPSILON);
|
||||
assertEquals("imported fin rotation does not match.", .123d, fins.getBaseRotation(), EPSILON);
|
||||
}
|
||||
|
||||
|
||||
|
@ -2,22 +2,209 @@ package net.sf.openrocket.rocketcomponent;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import net.sf.openrocket.material.Material;
|
||||
import org.junit.Test;
|
||||
|
||||
import net.sf.openrocket.rocketcomponent.position.*;
|
||||
import net.sf.openrocket.util.Coordinate;
|
||||
import net.sf.openrocket.util.BaseTestCase.BaseTestCase;
|
||||
|
||||
public class FinSetTest extends BaseTestCase {
|
||||
|
||||
private static final double EPSILON = 1E-8;
|
||||
|
||||
@Test
|
||||
public void testMultiplicity() {
|
||||
final TrapezoidFinSet trapFins = new TrapezoidFinSet();
|
||||
assertEquals(1, trapFins.getFinCount());
|
||||
|
||||
final FreeformFinSet fffins = new FreeformFinSet();
|
||||
assertEquals(1, fffins.getFinCount());
|
||||
|
||||
final EllipticalFinSet efins = new EllipticalFinSet();
|
||||
assertEquals(1, efins.getFinCount());
|
||||
final EllipticalFinSet fins = new EllipticalFinSet();
|
||||
assertEquals(1, fins.getFinCount());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* sweep= 0.02 | tipChord = 0.02
|
||||
* | | |
|
||||
* | +------+ ----------
|
||||
* | / \
|
||||
* | / \ height = 0.05
|
||||
* | / \
|
||||
* / \
|
||||
* __________/________________\_____ length == rootChord == 0.06
|
||||
* | |
|
||||
* | | tab height = 0.02
|
||||
* | |
|
||||
* +--------+ tab length = 0.02
|
||||
* position = 0.0 via middle
|
||||
*
|
||||
* Fin Area = 0.05 * ( (0.2 + 0.06)/2) = 0.0
|
||||
*/
|
||||
private static FinSet createSimpleFin() {
|
||||
|
||||
TrapezoidFinSet fins = new TrapezoidFinSet(1, 0.06, 0.02, 0.02, 0.05);
|
||||
fins.setName("test fins");
|
||||
fins.setAxialOffset(AxialMethod.MIDDLE, 0.0);
|
||||
fins.setMaterial(Material.newMaterial(Material.Type.BULK, "Fin-Test-Material", 1.0, true));
|
||||
fins.setThickness(0.005); // == 5 mm
|
||||
|
||||
fins.setTabLength(0.02);
|
||||
fins.setTabOffsetMethod(AxialMethod.TOP);
|
||||
fins.setTabOffset(0.02);
|
||||
|
||||
fins.setFilletRadius(0.0);
|
||||
|
||||
return fins;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTabLocation() {
|
||||
final FinSet fins = FinSetTest.createSimpleFin();
|
||||
assertEquals("incorrect fin length:", 0.06, fins.getLength(), EPSILON);
|
||||
assertEquals("incorrect fin tab length:", 0.02, fins.getTabLength(), EPSILON);
|
||||
|
||||
final double expFront = 0.02;
|
||||
final AxialMethod[] methods = AxialMethod.axialOffsetMethods;
|
||||
final double[] expShift = {0.02, 0.0, -0.02};
|
||||
for( int caseIndex=0; caseIndex < methods.length; ++caseIndex ){
|
||||
double actFront = fins.getTabFrontEdge();
|
||||
assertEquals(" Front edge doesn't match!", expFront, actFront, EPSILON);
|
||||
|
||||
// update
|
||||
fins.setTabOffsetMethod( methods[caseIndex]);
|
||||
|
||||
//query
|
||||
double actShift = fins.getTabOffset();
|
||||
assertEquals(String.format("Offset doesn't match for: %s \n", methods[caseIndex].name()), expShift[caseIndex], actShift, EPSILON);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTabGetAs(){
|
||||
final FinSet fins = FinSetTest.createSimpleFin();
|
||||
assertEquals("incorrect fin length:", 0.06, fins.getLength(), EPSILON);
|
||||
assertEquals("incorrect fin tab length:", 0.02, fins.getTabLength(), EPSILON);
|
||||
|
||||
// TOP -> native(TOP)
|
||||
fins.setTabOffsetMethod(AxialMethod.TOP);
|
||||
fins.setTabOffset(0.0);
|
||||
|
||||
assertEquals("Setting by TOP method failed!", 0.0, fins.getTabFrontEdge(), EPSILON);
|
||||
assertEquals("Setting by TOP method failed!", 0.0, fins.getTabOffset(), EPSILON);
|
||||
|
||||
// MIDDLE -> native
|
||||
fins.setTabOffsetMethod(AxialMethod.MIDDLE);
|
||||
fins.setTabOffset(0.0);
|
||||
assertEquals("Setting by TOP method failed!", 0.02, fins.getTabFrontEdge(), EPSILON);
|
||||
assertEquals("Setting by TOP method failed!", 0.0, fins.getTabOffset(), EPSILON);
|
||||
|
||||
// BOTTOM -> native
|
||||
fins.setTabOffsetMethod(AxialMethod.BOTTOM);
|
||||
fins.setTabOffset(0.0);
|
||||
|
||||
assertEquals("Setting by TOP method failed!", 0.04, fins.getTabFrontEdge(), EPSILON);
|
||||
assertEquals("Setting by TOP method failed!", 0.0, fins.getTabOffset(), EPSILON);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTabLocationUpdate() {
|
||||
final FinSet fins = FinSetTest.createSimpleFin();
|
||||
assertEquals("incorrect fin length:", 0.06, fins.getLength(), EPSILON);
|
||||
assertEquals("incorrect fin tab length:", 0.02, fins.getTabLength(), EPSILON);
|
||||
|
||||
// TOP -> native(TOP)
|
||||
fins.setTabOffsetMethod(AxialMethod.MIDDLE);
|
||||
fins.setTabOffset(0.0);
|
||||
|
||||
assertEquals("Setting by TOP method failed!", 0.0, fins.getTabOffset(), EPSILON);
|
||||
assertEquals("Setting by TOP method failed!", 0.02, fins.getTabFrontEdge(), EPSILON);
|
||||
|
||||
((TrapezoidFinSet)fins).setRootChord(0.08);
|
||||
|
||||
assertEquals("Front edge doesn't match after adjusting root chord...", 0.03, fins.getTabFrontEdge(), EPSILON);
|
||||
assertEquals("Offset doesn't match after adjusting root chord....", 0.0, fins.getTabOffset(), EPSILON);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAreaCalculationsSingleIncrement() {
|
||||
Coordinate[] basicPoints = {
|
||||
new Coordinate(0.00, 0.0),
|
||||
new Coordinate(0.06, 0.06),
|
||||
new Coordinate(0.06, 0.0),
|
||||
new Coordinate(0.00, 0.0) };
|
||||
//
|
||||
// [1] +
|
||||
// /|
|
||||
// / |
|
||||
// [0] +--+ [2]
|
||||
// [3]
|
||||
//
|
||||
|
||||
final double expArea = 0.06 * 0.06 * 0.5;
|
||||
final Coordinate actCentroid = FinSet.calculateCurveIntegral(basicPoints);
|
||||
assertEquals(" basic area doesn't match...", expArea, actCentroid.weight, EPSILON);
|
||||
assertEquals(" basic centroid x doesn't match: ", 0.04, actCentroid.x, 1e-8);
|
||||
assertEquals(" basic centroid y doesn't match: ", 0.02, actCentroid.y, 1e-8);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAreaCalculationsDoubleIncrement() {
|
||||
Coordinate[] basicPoints = {
|
||||
new Coordinate(0.00, 0.0),
|
||||
new Coordinate(0.06, 0.06),
|
||||
new Coordinate(0.12, 0.0),
|
||||
new Coordinate(0.00, 0.0) };
|
||||
//
|
||||
// [1] +
|
||||
// / \
|
||||
// / \
|
||||
// [0] +-----+ [2]
|
||||
// [3]
|
||||
//
|
||||
|
||||
final double expArea = 0.06 * 0.12 * 0.5;
|
||||
final Coordinate actCentroid = FinSet.calculateCurveIntegral(basicPoints);
|
||||
assertEquals(" basic area doesn't match...", expArea, actCentroid.weight, EPSILON);
|
||||
assertEquals(" basic centroid x doesn't match: ", 0.06, actCentroid.x, 1e-8);
|
||||
assertEquals(" basic centroid y doesn't match: ", 0.02, actCentroid.y, 1e-8);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testAreaCalculations() {
|
||||
Coordinate[] basicPoints = {
|
||||
new Coordinate(0.00, 0.0),
|
||||
new Coordinate(0.02, 0.05),
|
||||
new Coordinate(0.04, 0.05),
|
||||
new Coordinate(0.06, 0.0),
|
||||
new Coordinate(0.00, 0.0) };
|
||||
/*
|
||||
* [1] +--+ [2]
|
||||
* / \
|
||||
* / \
|
||||
* [0] +--------+ [3]
|
||||
* [4]
|
||||
*/
|
||||
final double expArea = 0.04 * 0.05;
|
||||
final Coordinate actCentroid = FinSet.calculateCurveIntegral(basicPoints);
|
||||
assertEquals(" basic area doesn't match...", expArea, actCentroid.weight, EPSILON);
|
||||
assertEquals(" basic centroid x doesn't match: ", 0.03000, actCentroid.x, 1e-8);
|
||||
assertEquals(" basic centroid y doesn't match: ", 0.020833333, actCentroid.y, 1e-8);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFinInstanceAngles() {
|
||||
FinSet fins = createSimpleFin();
|
||||
fins.setBaseRotation(Math.PI/6); // == 30d
|
||||
fins.setInstanceCount(3); // == 120d between each fin
|
||||
// => [ 30, 150, 270 ]
|
||||
// => PI*[ 1/6, 5/6, 9/6 ]
|
||||
// => [ .523, 2.61, 4.71 ]
|
||||
|
||||
final double[] instanceAngles = fins.getInstanceAngles();
|
||||
assertEquals( (1./6.)* Math.PI, fins.getBaseRotation(), EPSILON);
|
||||
assertEquals( (1./6.)* Math.PI, fins.getAngleOffset(), EPSILON);
|
||||
|
||||
assertEquals((1./6.)* Math.PI, instanceAngles[0], EPSILON);
|
||||
assertEquals((5./6.)* Math.PI, instanceAngles[1], EPSILON);
|
||||
assertEquals((9./6.)* Math.PI, instanceAngles[2], EPSILON);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -4,11 +4,189 @@ import static org.junit.Assert.assertEquals;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import net.sf.openrocket.material.Material;
|
||||
import net.sf.openrocket.rocketcomponent.position.*;
|
||||
|
||||
import net.sf.openrocket.util.Coordinate;
|
||||
import net.sf.openrocket.util.BaseTestCase.BaseTestCase;
|
||||
|
||||
public class TrapezoidFinSetTest extends BaseTestCase {
|
||||
|
||||
|
||||
private static final double EPSILON = 1E-8;
|
||||
|
||||
private Rocket createSimpleTrapezoidalFin() {
|
||||
final Rocket rkt = new Rocket();
|
||||
final AxialStage stg = new AxialStage();
|
||||
rkt.addChild(stg);
|
||||
BodyTube body = new BodyTube(0.2, 0.1);
|
||||
stg.addChild(body);
|
||||
TrapezoidFinSet fins = new TrapezoidFinSet(1, 0.06, 0.02, 0.02, 0.05);
|
||||
//
|
||||
// sweep= 0.02 | tipChord = 0.02
|
||||
// | | |
|
||||
// | +------+ ----------
|
||||
// | / \
|
||||
// | / \ height = 0.05
|
||||
// | / \
|
||||
// / \
|
||||
// __________/________________\_____ length == rootChord == 0.06
|
||||
// | |
|
||||
// | | tab height = 0.02
|
||||
// | |
|
||||
// +--------+ tab length = 0.02
|
||||
// position = 0.0 via middle
|
||||
//
|
||||
// Fin Area = 0.05 * ( (0.2 + 0.06)/2) = 0.0
|
||||
//
|
||||
fins.setAxialOffset(AxialMethod.MIDDLE, 0.0);
|
||||
fins.setMaterial(Material.newMaterial(Material.Type.BULK, "Fin-Test-Material", 1.0, true));
|
||||
fins.setThickness(0.005); // == 5 mm
|
||||
|
||||
body.addChild(fins);
|
||||
|
||||
fins.setTabLength(0.00);
|
||||
|
||||
fins.setFilletRadius(0.0);
|
||||
|
||||
rkt.enableEvents();
|
||||
return rkt;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMultiplicity() {
|
||||
final TrapezoidFinSet trapFins = new TrapezoidFinSet();
|
||||
assertEquals(1, trapFins.getFinCount());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGenerateTrapezoidalPoints() {
|
||||
final Rocket rkt = createSimpleTrapezoidalFin();
|
||||
FinSet fins = (FinSet) rkt.getChild(0).getChild(0).getChild(0);
|
||||
|
||||
// Fin length = 0.05
|
||||
// Tab Length = 0.01
|
||||
// +--+
|
||||
// / \
|
||||
// / \
|
||||
// +---+--------+---+
|
||||
//
|
||||
Coordinate[] actPoints = fins.getFinPoints();
|
||||
|
||||
Coordinate[] expPoints = { new Coordinate(0.00, 0.0),
|
||||
new Coordinate(0.02, 0.05),
|
||||
new Coordinate(0.04, 0.05),
|
||||
new Coordinate(0.06, 0.0),
|
||||
new Coordinate(0.00, 0.0) };
|
||||
|
||||
for (int index = 0; index < actPoints.length; ++index) {
|
||||
assertEquals(" generated fin point [" + index + "] doesn't match! ", expPoints[index].x, actPoints[index].x, EPSILON);
|
||||
assertEquals(" generated fin point [" + index + "] doesn't match!", expPoints[index].x, actPoints[index].x, EPSILON);
|
||||
assertEquals(" generated fin point [" + index + "] doesn't match!", expPoints[index].x, actPoints[index].x, EPSILON);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCGCalculation_simpleSquareFin() {
|
||||
final Rocket rkt = createSimpleTrapezoidalFin();
|
||||
final TrapezoidFinSet fins = (TrapezoidFinSet)rkt.getChild(0).getChild(0).getChild(0);
|
||||
|
||||
// This is a simple square fin with sides of 1.0.
|
||||
fins.setFinShape(0.1, 0.1, 0.0, 0.1, .005);
|
||||
|
||||
// should return a single-fin-planform area
|
||||
assertEquals("area calculation doesn't match: ", 0.01, fins.getPlanformArea(), 0.00001);
|
||||
|
||||
final double expSingleMass = 0.00005;
|
||||
final Coordinate singleCG = fins.getComponentCG();
|
||||
assertEquals("Fin mass is wrong! ", expSingleMass, singleCG.weight, EPSILON);
|
||||
assertEquals("Centroid x coordinate is wrong! ", 0.05, singleCG.x, EPSILON);
|
||||
assertEquals("Centroid y coordinate is wrong! ", 0.15, singleCG.y, EPSILON);
|
||||
|
||||
// should still return a single-fin-wetted area
|
||||
assertEquals(0.00005, fins.getComponentVolume(), 0.0000001);
|
||||
|
||||
{ // test instancing code
|
||||
// this should also trigger a recalculation
|
||||
fins.setFinCount(2);
|
||||
|
||||
// should still return a single-fin-planform area
|
||||
assertEquals(0.01, fins.getPlanformArea(), 0.00001);
|
||||
|
||||
Coordinate doubleCG = fins.getComponentCG();
|
||||
final double expDoubleMass = expSingleMass*2;
|
||||
assertEquals("Fin x2 mass does not change from single fin instance! ", expDoubleMass, doubleCG.weight, EPSILON);
|
||||
assertEquals(0.05, doubleCG.x, EPSILON);
|
||||
assertEquals(0.0, doubleCG.y, EPSILON);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testCGCalculations_finWithTab() throws IllegalFinPointException {
|
||||
final Rocket rkt = createSimpleTrapezoidalFin();
|
||||
FinSet fins = (FinSet) rkt.getChild(0).getChild(0).getChild(0);
|
||||
|
||||
fins.setTabLength(0.02);
|
||||
fins.setTabHeight(0.02);
|
||||
fins.setTabOffsetMethod(AxialMethod.MIDDLE);
|
||||
fins.setTabOffset(0.0);
|
||||
|
||||
assertEquals("Wetted Area does not match!", 0.0020, fins.getPlanformArea(), EPSILON);
|
||||
|
||||
final double expVol1 = 0.00001200;
|
||||
final double actVol1 = fins.getComponentVolume();
|
||||
assertEquals(" fin volume is incorrect", expVol1, actVol1, EPSILON);
|
||||
|
||||
Coordinate actCentroid1 = fins.getCG();
|
||||
assertEquals(" basic centroid x doesn't match: ", 0.03000, actCentroid1.x, EPSILON);
|
||||
assertEquals(" basic centroid y doesn't match: ", 0.11569444, actCentroid1.y, EPSILON);
|
||||
|
||||
{
|
||||
fins.setFinCount(2);
|
||||
final double expVol2 = expVol1 * 2;
|
||||
final double actVol2 = fins.getComponentVolume();
|
||||
assertEquals(" fin volume is incorrect", expVol2, actVol2, EPSILON);
|
||||
|
||||
Coordinate actCentroid2 = fins.getCG();
|
||||
// x coordinate will be the same....
|
||||
assertEquals(" basic centroid y doesn't match: ", 0.0, actCentroid2.y, EPSILON);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testFilletCalculations() {
|
||||
final Rocket rkt = createSimpleTrapezoidalFin();
|
||||
BodyTube body = (BodyTube) rkt.getChild(0).getChild(0);
|
||||
FinSet fins = (FinSet) rkt.getChild(0).getChild(0).getChild(0);
|
||||
|
||||
fins.setFilletRadius(0.005);
|
||||
fins.setFilletMaterial(Material.newMaterial(Material.Type.BULK, "Fillet-Test-Material", 1.0, true));
|
||||
|
||||
// used for fillet and edge calculations:
|
||||
//
|
||||
// [1] +--+ [2]
|
||||
// / \
|
||||
// / \
|
||||
// [0] +--------+ [3]
|
||||
//
|
||||
assertEquals("Body radius doesn't match: ", 0.1, body.getOuterRadius(), EPSILON);
|
||||
|
||||
final Coordinate actVolume = fins.calculateFilletVolumeCentroid();
|
||||
|
||||
assertEquals("Line volume doesn't match: ", 5.973e-07, actVolume.weight, EPSILON);
|
||||
|
||||
assertEquals("Line mass center.x doesn't match: ", 0.03, actVolume.x, EPSILON);
|
||||
assertEquals("Line mass center.y doesn't match: ", 0.101, actVolume.y, EPSILON);
|
||||
|
||||
|
||||
{ // and then, check that the fillet volume feeds into a correct overall CG:
|
||||
Coordinate actCentroid = fins.getCG();
|
||||
assertEquals("Complete centroid x doesn't match: ", 0.03000, actCentroid.x, EPSILON);
|
||||
assertEquals("Complete centroid y doesn't match: ", 0.11971548, actCentroid.y, EPSILON);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTrapezoidCGComputation() {
|
||||
|
||||
@ -19,7 +197,7 @@ public class TrapezoidFinSetTest extends BaseTestCase {
|
||||
fins.setFinShape(1.0, 1.0, 0.0, 1.0, .005);
|
||||
|
||||
Coordinate coords = fins.getCG();
|
||||
assertEquals(1.0, fins.getFinArea(), 0.001);
|
||||
assertEquals(1.0, fins.getPlanformArea(), 0.001);
|
||||
assertEquals(0.5, coords.x, 0.001);
|
||||
assertEquals(0.5, coords.y, 0.001);
|
||||
}
|
||||
@ -36,7 +214,7 @@ public class TrapezoidFinSetTest extends BaseTestCase {
|
||||
fins.setFinShape(1.0, 0.5, 0.0, 1.0, .005);
|
||||
|
||||
Coordinate coords = fins.getCG();
|
||||
assertEquals(0.75, fins.getFinArea(), 0.001);
|
||||
assertEquals(0.75, fins.getPlanformArea(), 0.001);
|
||||
assertEquals(0.3889, coords.x, 0.001);
|
||||
assertEquals(0.4444, coords.y, 0.001);
|
||||
}
|
||||
|
@ -201,7 +201,7 @@ public abstract class FinSetConfig extends RocketComponentConfig {
|
||||
label.setToolTipText(trans.get("FinSetConfig.ttip.Tabposition"));
|
||||
panel.add(label, "gapleft para");
|
||||
|
||||
final DoubleModel mts = new DoubleModel(component, "TabShift", UnitGroup.UNITS_LENGTH);
|
||||
final DoubleModel mts = new DoubleModel(component, "TabOffset", UnitGroup.UNITS_LENGTH);
|
||||
component.addChangeListener( mts);
|
||||
spin = new JSpinner(mts.getSpinnerModel());
|
||||
spin.setEditor(new SpinnerEditor(spin));
|
||||
@ -215,7 +215,7 @@ public abstract class FinSetConfig extends RocketComponentConfig {
|
||||
panel.add(label, "right, gapright unrel");
|
||||
|
||||
|
||||
final EnumModel<AxialMethod> em = new EnumModel<>(component, "TabRelativePosition");
|
||||
final EnumModel<AxialMethod> em = new EnumModel<>(component, "TabOffsetMethod");
|
||||
|
||||
JComboBox<AxialMethod> enumCombo = new JComboBox<>(em);
|
||||
|
||||
|
@ -340,10 +340,6 @@ public class FreeformFinSetConfig extends FinSetConfig {
|
||||
List<Coordinate> points = importer.getPoints(chooser.getSelectedFile());
|
||||
document.startUndo(trans.get("CustomFinImport.undo"));
|
||||
finset.setPoints( points);
|
||||
} catch (IllegalFinPointException e) {
|
||||
log.warn("Error storing fin points", e);
|
||||
JOptionPane.showMessageDialog(this, trans.get("CustomFinImport.error.badimage"),
|
||||
trans.get("CustomFinImport.error.title"), JOptionPane.ERROR_MESSAGE);
|
||||
} catch (IOException e) {
|
||||
log.warn("Error loading file", e);
|
||||
JOptionPane.showMessageDialog(this, e.getLocalizedMessage(),
|
||||
@ -426,11 +422,7 @@ public class FreeformFinSetConfig extends FinSetConfig {
|
||||
}
|
||||
|
||||
Point2D.Double point = getCoordinates(event);
|
||||
try {
|
||||
finset.setPoint(dragIndex, point.x, point.y);
|
||||
} catch (IllegalFinPointException ignore) {
|
||||
log.debug("Ignoring IllegalFinPointException while dragging, dragIndex=" + dragIndex + " x=" + point.x + " y=" + point.y);
|
||||
}
|
||||
finset.setPoint(dragIndex, point.x, point.y);
|
||||
|
||||
updateFields();
|
||||
}
|
||||
@ -581,8 +573,6 @@ public class FreeformFinSetConfig extends FinSetConfig {
|
||||
updateFields();
|
||||
} catch (NumberFormatException ignore) {
|
||||
log.warn("ignoring NumberFormatException while editing a Freeform Fin");
|
||||
} catch (IllegalFinPointException ignore) {
|
||||
log.warn("ignoring IllegalFinPointException while editing a Freeform Fin");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -115,7 +115,7 @@ public class ScaleDialog extends JDialog {
|
||||
addScaler(FinSet.class, "Thickness");
|
||||
addScaler(FinSet.class, "TabHeight");
|
||||
addScaler(FinSet.class, "TabLength");
|
||||
addScaler(FinSet.class, "TabShift");
|
||||
addScaler(FinSet.class, "TabOffset");
|
||||
|
||||
// TrapezoidFinSet
|
||||
addScaler(TrapezoidFinSet.class, "Sweep");
|
||||
@ -623,11 +623,9 @@ public class ScaleDialog extends JDialog {
|
||||
for (int i = 0; i < points.length; i++) {
|
||||
points[i] = points[i].multiply(multiplier);
|
||||
}
|
||||
try {
|
||||
finset.setPoints(points);
|
||||
} catch (IllegalFinPointException e) {
|
||||
throw new BugException("Failed to set points after scaling, original=" + Arrays.toString(finset.getFinPoints()) + " scaled=" + Arrays.toString(points), e);
|
||||
}
|
||||
|
||||
finset.setPoints(points);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -128,17 +128,17 @@ public class ComponentAddButtons extends JPanel implements Scrollable {
|
||||
//// Transition
|
||||
new BodyComponentButton(Transition.class, trans.get("compaddbuttons.Transition")),
|
||||
//// Trapezoidal
|
||||
new FinButton(TrapezoidFinSet.class, trans.get("compaddbuttons.Trapezoidal")), // TODO: MEDIUM: freer fin placing
|
||||
new ComponentButton(TrapezoidFinSet.class, trans.get("compaddbuttons.Trapezoidal")), // TODO: MEDIUM: freer fin placing
|
||||
//// Elliptical
|
||||
new FinButton(EllipticalFinSet.class, trans.get("compaddbuttons.Elliptical")),
|
||||
new ComponentButton(EllipticalFinSet.class, trans.get("compaddbuttons.Elliptical")),
|
||||
//// Freeform
|
||||
new FinButton(FreeformFinSet.class, trans.get("compaddbuttons.Freeform")),
|
||||
new ComponentButton(FreeformFinSet.class, trans.get("compaddbuttons.Freeform")),
|
||||
//// Freeform
|
||||
new FinButton(TubeFinSet.class, trans.get("compaddbuttons.Tubefin")),
|
||||
//// Rail Button // TODO: implement drawing graphics for the component
|
||||
new FinButton( RailButton.class, trans.get("compaddbuttons.RailButton")),
|
||||
new ComponentButton(TubeFinSet.class, trans.get("compaddbuttons.Tubefin")),
|
||||
//// Rail Button
|
||||
new ComponentButton( RailButton.class, trans.get("compaddbuttons.RailButton")),
|
||||
//// Launch lug
|
||||
new FinButton(LaunchLug.class, trans.get("compaddbuttons.Launchlug")));
|
||||
new ComponentButton(LaunchLug.class, trans.get("compaddbuttons.Launchlug")));
|
||||
row++;
|
||||
|
||||
/////////////////////////////////////////////
|
||||
@ -627,38 +627,7 @@ public class ComponentAddButtons extends JPanel implements Scrollable {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Class for fin sets, that attach only to BodyTubes.
|
||||
*/
|
||||
private class FinButton extends ComponentButton {
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private static final long serialVersionUID = -219204844803871258L;
|
||||
|
||||
public FinButton(Class<? extends RocketComponent> c, String text) {
|
||||
super(c, text);
|
||||
}
|
||||
|
||||
public FinButton(String text, Icon enabled, Icon disabled) {
|
||||
super(text, enabled, disabled);
|
||||
}
|
||||
|
||||
public FinButton(String text) {
|
||||
super(text);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAddable(RocketComponent c) {
|
||||
if (c == null)
|
||||
return false;
|
||||
return (c.getClass().equals(BodyTube.class));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
///////// Scrolling functionality
|
||||
|
@ -2,56 +2,125 @@ package net.sf.openrocket.gui.rocketfigure;
|
||||
|
||||
import java.awt.Shape;
|
||||
import java.awt.geom.Path2D;
|
||||
import java.util.ArrayList;
|
||||
|
||||
import net.sf.openrocket.rocketcomponent.FinSet;
|
||||
import net.sf.openrocket.rocketcomponent.RocketComponent;
|
||||
import net.sf.openrocket.util.Coordinate;
|
||||
import net.sf.openrocket.util.MathUtil;
|
||||
import net.sf.openrocket.util.Transformation;
|
||||
|
||||
|
||||
public class FinSetShapes extends RocketComponentShape {
|
||||
|
||||
// TODO: LOW: Clustering is ignored (FinSet cannot currently be clustered)
|
||||
|
||||
|
||||
public static RocketComponentShape[] getShapesSide(
|
||||
net.sf.openrocket.rocketcomponent.RocketComponent component,
|
||||
RocketComponent component,
|
||||
Transformation transformation,
|
||||
Coordinate instanceAbsoluteLocation) {
|
||||
net.sf.openrocket.rocketcomponent.FinSet finset = (net.sf.openrocket.rocketcomponent.FinSet)component;
|
||||
|
||||
Coordinate finSetFront = instanceAbsoluteLocation;
|
||||
Coordinate finPoints[] = finset.getFinPointsWithTab();
|
||||
Coordinate componentAbsoluteLocation) {
|
||||
FinSet finset = (FinSet)component;
|
||||
|
||||
Transformation cantRotation = finset.getCantRotation();
|
||||
finPoints = cantRotation.transform(finPoints);
|
||||
finPoints = transformation.transform(finPoints);
|
||||
int finCount = finset.getFinCount();
|
||||
// TODO: MEDIUM: sloping radius
|
||||
final double rFront = finset.getFinFront().y;
|
||||
|
||||
Transformation cantRotation = finset.getCantRotation();
|
||||
Transformation baseRotation = new Transformation(finset.getBaseRotation(), 0, 0); // rotation about x-axis
|
||||
Transformation radialTranslation = new Transformation( 0, rFront, 0);
|
||||
Transformation finRotation = finset.getFinRotationTransformation();
|
||||
Transformation compositeTransform = baseRotation
|
||||
.applyTransformation( radialTranslation)
|
||||
.applyTransformation( cantRotation)
|
||||
.applyTransformation( transformation);
|
||||
|
||||
|
||||
Coordinate finSetFront = componentAbsoluteLocation;
|
||||
Coordinate finPoints[] = finset.getFinPoints();
|
||||
Coordinate tabPoints[] = finset.getTabPoints();
|
||||
Coordinate basePoints[] = finset.getRootPoints();
|
||||
|
||||
// Translate & rotate points into place
|
||||
finPoints = compositeTransform.transform( finPoints );
|
||||
tabPoints = compositeTransform.transform( tabPoints);
|
||||
basePoints = compositeTransform.transform( basePoints );
|
||||
|
||||
// Generate shapes
|
||||
Path2D.Float p;
|
||||
{
|
||||
// Make polygon
|
||||
p = new Path2D.Float();
|
||||
for (int i=0; i<finPoints.length; i++) {
|
||||
Coordinate c = finSetFront.add(finPoints[i]);
|
||||
|
||||
if (i==0)
|
||||
p.moveTo(c.x, c.y);
|
||||
else
|
||||
p.lineTo(c.x, c.y);
|
||||
}
|
||||
|
||||
p.closePath();
|
||||
}
|
||||
|
||||
return new RocketComponentShape[] {new RocketComponentShape(p, finset)};
|
||||
ArrayList<RocketComponentShape> shapeList = new ArrayList<>();
|
||||
for (int finNum=0; finNum<finCount; finNum++) {
|
||||
Coordinate curPoint;
|
||||
|
||||
// Make fin polygon
|
||||
Path2D.Float finShape = new Path2D.Float();
|
||||
for (int i=0; i<finPoints.length; i++) {
|
||||
curPoint = finSetFront.add(finPoints[i]);
|
||||
|
||||
if (i==0)
|
||||
finShape.moveTo(curPoint.x, curPoint.y);
|
||||
else
|
||||
finShape.lineTo(curPoint.x, curPoint.y);
|
||||
}
|
||||
shapeList.add( new RocketComponentShape( finShape, finset));
|
||||
|
||||
// draw fin-body intersection line
|
||||
double angle_rad = finset.getBaseRotation() + ((double)finNum) / ((double)finCount) *2*Math.PI;
|
||||
// only draw body-root intersection line if it's not hidden-- i.e. is not at {0,PI/2,PI,3/2*PI} angles
|
||||
final boolean drawRoot= (0.05 < Math.abs( angle_rad % (Math.PI/2.0)));
|
||||
boolean simpleRoot = finset.isRootStraight( );
|
||||
if( drawRoot){
|
||||
if( simpleRoot){
|
||||
// draws a straight-line connection from the end back to the start
|
||||
finShape.closePath();
|
||||
}else{
|
||||
// this implies a curved fin-body intersection
|
||||
// ... which is more complicated.
|
||||
Path2D.Float rootShape = new Path2D.Float();
|
||||
for (int i=0; i< basePoints.length; i++) {
|
||||
curPoint = finSetFront.add( basePoints[i]);
|
||||
|
||||
if (i==0)
|
||||
rootShape.moveTo(curPoint.x, curPoint.y);
|
||||
else
|
||||
rootShape.lineTo(curPoint.x, curPoint.y);
|
||||
}
|
||||
|
||||
shapeList.add( new RocketComponentShape( rootShape, finset));
|
||||
}
|
||||
}
|
||||
|
||||
// Make tab polygon
|
||||
Path2D.Float tabShape = new Path2D.Float();
|
||||
if( 0 < tabPoints.length ){
|
||||
for (int i=0; i<tabPoints.length; i++) {
|
||||
curPoint = finSetFront.add(tabPoints[i]);
|
||||
|
||||
if (i==0)
|
||||
tabShape.moveTo(curPoint.x, curPoint.y);
|
||||
else
|
||||
tabShape.lineTo(curPoint.x, curPoint.y);
|
||||
}
|
||||
|
||||
// the fin tab / body surface line should lay on the fin-root line above
|
||||
|
||||
shapeList.add( new RocketComponentShape( tabShape, finset));
|
||||
}
|
||||
|
||||
// Rotate fin, tab coordinates
|
||||
finPoints = finRotation.transform(finPoints);
|
||||
tabPoints = finRotation.transform(tabPoints);
|
||||
basePoints = finRotation.transform( basePoints);
|
||||
}
|
||||
|
||||
return shapeList.toArray(new RocketComponentShape[0]);
|
||||
}
|
||||
|
||||
public static RocketComponentShape[] getShapesBack(
|
||||
net.sf.openrocket.rocketcomponent.RocketComponent component,
|
||||
RocketComponent component,
|
||||
Transformation transformation,
|
||||
Coordinate location) {
|
||||
|
||||
net.sf.openrocket.rocketcomponent.FinSet finset = (net.sf.openrocket.rocketcomponent.FinSet)component;
|
||||
|
||||
FinSet finset = (FinSet)component;
|
||||
|
||||
Shape[] toReturn;
|
||||
|
||||
if (MathUtil.equals(finset.getCantAngle(),0)){
|
||||
@ -65,7 +134,7 @@ public class FinSetShapes extends RocketComponentShape {
|
||||
}
|
||||
|
||||
|
||||
private static Shape[] uncantedShapesBack(net.sf.openrocket.rocketcomponent.FinSet finset,
|
||||
private static Shape[] uncantedShapesBack(FinSet finset,
|
||||
Transformation transformation,
|
||||
Coordinate finFront) {
|
||||
|
||||
@ -101,12 +170,11 @@ public class FinSetShapes extends RocketComponentShape {
|
||||
|
||||
|
||||
// TODO: LOW: Jagged shapes from back draw incorrectly.
|
||||
private static Shape[] cantedShapesBack(net.sf.openrocket.rocketcomponent.FinSet finset,
|
||||
private static Shape[] cantedShapesBack(FinSet finset,
|
||||
Transformation transformation,
|
||||
Coordinate location) {
|
||||
int i;
|
||||
int fins = finset.getFinCount();
|
||||
// double radius = finset.getBodyRadius();
|
||||
double thickness = finset.getThickness();
|
||||
|
||||
Transformation cantRotation = finset.getCantRotation();
|
||||
@ -172,15 +240,7 @@ public class FinSetShapes extends RocketComponentShape {
|
||||
return s;
|
||||
}
|
||||
|
||||
|
||||
|
||||
private static void transformPoints(Coordinate[] array, Transformation t) {
|
||||
for (int i=0; i < array.length; i++) {
|
||||
array[i] = t.transform(array[i]);
|
||||
}
|
||||
}
|
||||
|
||||
private static Shape makePolygonBack(Coordinate[] array, net.sf.openrocket.rocketcomponent.FinSet finset,
|
||||
private static Shape makePolygonBack(Coordinate[] array, FinSet finset,
|
||||
Transformation t, Coordinate location) {
|
||||
Path2D.Float p;
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user