[feat] FlightConfiguration may now generate an InstanceMap

This commit is contained in:
Daniel_M_Williams 2019-01-05 18:09:39 -05:00
parent 7b28923659
commit efabe81790
6 changed files with 336 additions and 14 deletions

View File

@ -4,6 +4,7 @@ import java.util.ArrayDeque;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import org.slf4j.Logger;
@ -18,6 +19,7 @@ import net.sf.openrocket.util.BoundingBox;
import net.sf.openrocket.util.Coordinate;
import net.sf.openrocket.util.MathUtil;
import net.sf.openrocket.util.Monitorable;
import net.sf.openrocket.util.Transformation;
/**
@ -36,9 +38,9 @@ public class FlightConfiguration implements FlightConfigurableParameter<FlightCo
protected final Rocket rocket;
protected final FlightConfigurationId fcid;
private static int instanceCount=0;
private static int configurationInstanceCount=0;
// made public for testing.... there is probably a better way
public final int instanceNumber;
public final int configurationInstanceId;
private class StageFlags implements Cloneable {
public boolean active = true;
@ -83,7 +85,7 @@ public class FlightConfiguration implements FlightConfigurableParameter<FlightCo
}
this.rocket = rocket;
this.configurationName = null;
this.instanceNumber = instanceCount++;
this.configurationInstanceId = configurationInstanceCount++;
updateStages();
updateMotors();
@ -213,6 +215,49 @@ public class FlightConfiguration implements FlightConfigurableParameter<FlightCo
return toReturn;
}
/*
* Generates a read-only, instance-aware collection of the components for this rocket & configuration
*
* TODO: swap in this function for the 'getActiveComponents() function, above; ONLY WHEN READY / MATURE!
*/
public InstanceMap getActiveInstances() {
InstanceMap contexts = new InstanceMap();
getContextListAt( this.rocket, contexts, Transformation.IDENTITY);
return contexts;
}
private InstanceMap getContextListAt(final RocketComponent component, final InstanceMap results, final Transformation parentTransform ){
final int instanceCount = component.getInstanceCount();
final Coordinate[] allOffsets = component.getInstanceOffsets();
final double[] allAngles = component.getInstanceAngles();
final boolean active = this.isComponentActive(component);
final Transformation compLocTransform = Transformation.getTranslationTransform( component.getPosition() );
final Transformation componentTransform = parentTransform.applyTransformation(compLocTransform);
// generate the Instance's Context:
for(int currentInstanceNumber=0; currentInstanceNumber < instanceCount; currentInstanceNumber++) {
final Coordinate instanceOffset = allOffsets[currentInstanceNumber];
final Transformation offsetTransform = Transformation.getTranslationTransform( instanceOffset );
final double instanceAngle = allAngles[currentInstanceNumber];
final Transformation angleTransform = Transformation.getAxialRotation(instanceAngle);
final Transformation currentTransform = componentTransform.applyTransformation(offsetTransform)
.applyTransformation(angleTransform);
// constructs entry in-place
results.emplace(component, active, currentInstanceNumber, currentTransform);
for(RocketComponent child : component.getChildren()) {
getContextListAt(child, results, currentTransform);
}
}
return results;
}
public List<AxialStage> getActiveStages() {
List<AxialStage> activeStages = new ArrayList<>();
@ -554,14 +599,14 @@ public class FlightConfiguration implements FlightConfigurableParameter<FlightCo
}
public String toDebug() {
return this.fcid.toDebug()+" (#"+instanceNumber+") "+ getOneLineMotorDescription();
return this.fcid.toDebug()+" (#"+configurationInstanceId+") "+ getOneLineMotorDescription();
}
// DEBUG / DEVEL
public String toStageListDetail() {
StringBuilder buf = new StringBuilder();
buf.append(String.format("\nDumping %d stages for config: %s: (%s)(#: %d)\n",
stages.size(), getName(), getId().toShortKey(), instanceNumber));
stages.size(), getName(), getId().toShortKey(), configurationInstanceId));
final String fmt = " [%-2s][%4s]: %6s \n";
buf.append(String.format(fmt, "#", "?actv", "Name"));
for (StageFlags flags : stages.values()) {
@ -576,7 +621,7 @@ public class FlightConfiguration implements FlightConfigurableParameter<FlightCo
public String toMotorDetail(){
StringBuilder buf = new StringBuilder();
buf.append(String.format("\nDumping %2d Motors for configuration %s (%s)(#: %s)\n",
motors.size(), getName(), getId().toShortKey(), this.instanceNumber));
motors.size(), getName(), getId().toShortKey(), this.configurationInstanceId));
for( MotorConfiguration curConfig : this.motors.values() ){
boolean active=this.isStageActive( curConfig.getMount().getStage().getStageNumber());

View File

@ -0,0 +1,59 @@
package net.sf.openrocket.rocketcomponent;
import net.sf.openrocket.util.Coordinate;
import net.sf.openrocket.util.Transformation;
/**
*
* @author teyrana (aka Daniel Williams) <equipoise@gmail.com>
*
*/
public class InstanceContext {
// =========== Public Functions ========================
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
InstanceContext other = (InstanceContext) obj;
return (component.equals(other.component) && transform.equals(other.transform));
}
@Override
public int hashCode() {
return (int) (component.hashCode());
}
public InstanceContext(final RocketComponent _component, final boolean _active, final int _instanceNumber, final Transformation _transform) {
component = _component;
active = _active;
instanceNumber = _instanceNumber;
transform = _transform;
}
@Override
public String toString() {
return String.format("Context for %s #%d", component);
}
public Coordinate getLocation() {
return transform.transform(Coordinate.ZERO);
}
// =========== Instance Member Variables ========================
// ==== public ====
final public RocketComponent component;
final public boolean active;
final public int instanceNumber;
final public Transformation transform;
// =========== Private Instance Functions ========================
}

View File

@ -0,0 +1,73 @@
package net.sf.openrocket.rocketcomponent;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import net.sf.openrocket.util.Transformation;
/**
*
* @author teyrana (aka Daniel Williams) <equipoise@gmail.com>
*
*/
public class InstanceMap extends HashMap<RocketComponent, ArrayList<InstanceContext>> {
// =========== Public Functions ========================
// public InstanceMap() {}
public int count(final RocketComponent key) {
if(containsKey(key)){
return get(key).size();
}else {
return 0;
}
}
public void emplace(final RocketComponent component, boolean active, int number, final Transformation xform) {
final RocketComponent key = component;
if(!containsKey(component)) {
put(key, new ArrayList<>());
}
final InstanceContext context = new InstanceContext(component, active, number, xform);
get(key).add(context);
}
public List<InstanceContext> getInstanceContexts(final RocketComponent key) {
return get(key);
}
// this is primarily for debugging.
@Override
public String toString() {
StringBuffer buffer = new StringBuffer();
int outerIndex = 0;
buffer.append(">> Printing InstanceMap:\n");
for(Map.Entry<RocketComponent, ArrayList<InstanceContext>> entry: entrySet() ) {
final RocketComponent key = entry.getKey();
final ArrayList<InstanceContext> contexts = entry.getValue();
buffer.append(String.format("....[% 2d]:[%s]\n", outerIndex, key.getName()));
outerIndex++;
int innerIndex = 0;
for(InstanceContext ctxt: contexts ) {
buffer.append(String.format("........[@% 2d][% 2d] %s\n", innerIndex, ctxt.instanceNumber, ctxt.getLocation().toPreciseString()));
innerIndex++;
}
}
return buffer.toString();
}
// =========== Instance Member Variables ========================
// =========== Private Instance Functions ========================
}

View File

@ -32,7 +32,6 @@ import net.sf.openrocket.rocketcomponent.FinSet.CrossSection;
import net.sf.openrocket.rocketcomponent.FlightConfiguration;
import net.sf.openrocket.rocketcomponent.FlightConfigurationId;
import net.sf.openrocket.rocketcomponent.FreeformFinSet;
import net.sf.openrocket.rocketcomponent.IllegalFinPointException;
import net.sf.openrocket.rocketcomponent.InnerTube;
import net.sf.openrocket.rocketcomponent.InternalComponent;
import net.sf.openrocket.rocketcomponent.LaunchLug;
@ -862,7 +861,7 @@ public class TestRockets {
// ====== Payload Stage ======
// ====== ====== ====== ======
AxialStage payloadStage = new AxialStage();
payloadStage.setName("Payload Fairing");
payloadStage.setName("Payload Fairing Stage");
rocket.addChild(payloadStage);
{

View File

@ -5,14 +5,16 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import java.util.List;
import org.junit.Test;
import net.sf.openrocket.util.Coordinate;
import net.sf.openrocket.util.MathUtil;
import net.sf.openrocket.util.TestRockets;
import net.sf.openrocket.util.BaseTestCase.BaseTestCase;
public class FlightConfigurationTest extends BaseTestCase {
private final static double EPSILON = MathUtil.EPSILON*1E3;
/**
@ -155,7 +157,6 @@ public class FlightConfigurationTest extends BaseTestCase {
// test explicitly setting all stages active
config.setAllStages();
}
/**
@ -326,5 +327,151 @@ public class FlightConfigurationTest extends BaseTestCase {
assertThat("active motor count doesn't match: ", actualMotorCount, equalTo(expectedMotorCount));
}
@Test
public void testIterateComponents() {
Rocket rocket = TestRockets.makeFalcon9Heavy();
FlightConfiguration selected = rocket.getSelectedConfiguration();
selected.clearAllStages();
selected.toggleStage(2);
// vvvv Test Target vvvv
InstanceMap instances = selected.getActiveInstances();
// ^^^^ Test Target ^^^^
// Payload Stage
final AxialStage coreStage = (AxialStage)rocket.getChild(1);
{ // Core Stage
final List<InstanceContext> coreStageContextList = instances.getInstanceContexts(coreStage);
final InstanceContext coreStageContext = coreStageContextList.get(0);
assertThat(coreStageContext.component.getClass(), equalTo(AxialStage.class));
assertThat(coreStageContext.component.getID(), equalTo(rocket.getChild(1).getID()));
assertThat(coreStageContext.component.getInstanceCount(), equalTo(1));
final Coordinate coreLocation = coreStageContext.getLocation();
assertEquals(coreLocation.x, 0.564, EPSILON);
assertEquals(coreLocation.y, 0.0, EPSILON);
assertEquals(coreLocation.z, 0.0, EPSILON);
//... skip uninteresting component
}
// Booster Stage
{ // instance #1
final ParallelStage boosterStage = (ParallelStage)coreStage.getChild(0).getChild(0);
final List<InstanceContext> boosterStageContextList = instances.getInstanceContexts(boosterStage);
final InstanceContext boosterStage0Context = boosterStageContextList.get(0);
assertThat(boosterStage0Context.component.getClass(), equalTo(ParallelStage.class));
assertThat(boosterStage0Context.component.getID(), equalTo(boosterStage.getID()));
assertThat(boosterStage0Context.instanceNumber, equalTo(0));
{
final Coordinate loc = boosterStage0Context.getLocation();
assertEquals(loc.x, 0.484, EPSILON);
assertEquals(loc.y, 0.077, EPSILON);
assertEquals(loc.z, 0.0, EPSILON);
}
final InstanceContext boosterStage1Context = boosterStageContextList.get(1);
assertThat(boosterStage1Context.component.getClass(), equalTo(ParallelStage.class));
assertThat(boosterStage1Context.component.getID(), equalTo(boosterStage.getID()));
assertThat(boosterStage1Context.instanceNumber, equalTo(1));
{
final Coordinate loc = boosterStage1Context.getLocation();
assertEquals(loc.x, 0.484, EPSILON);
assertEquals(loc.y, -0.077, EPSILON);
assertEquals(loc.z, 0.0, EPSILON);
}
{ // Booster Body:
final BodyTube boosterBody = (BodyTube)boosterStage.getChild(1);
final List<InstanceContext> boosterBodyContextList = instances.getInstanceContexts(boosterBody);
// this is the instance number rocket-wide
final InstanceContext boosterBodyContext = boosterBodyContextList.get(1);
// this is the instance number per-parent
assertThat(boosterBodyContext.instanceNumber, equalTo(0));
assertThat(boosterBodyContext.component.getClass(), equalTo(BodyTube.class));
final Coordinate bodyTubeLocation = boosterBodyContext.getLocation();
assertEquals(bodyTubeLocation.x, 0.564, EPSILON);
assertEquals(bodyTubeLocation.y, -0.077, EPSILON);
assertEquals(bodyTubeLocation.z, 0.0, EPSILON);
{ // Booster::Motor Tubes ( x2 x4)
final InnerTube boosterMMT = (InnerTube)boosterBody.getChild(0);
final List<InstanceContext> mmtContextList = instances.getInstanceContexts(boosterMMT);
assertEquals(8, mmtContextList.size());
final InstanceContext motorTubeContext0 = mmtContextList.get(4);
assertThat(motorTubeContext0.component.getClass(), equalTo(InnerTube.class));
assertThat(motorTubeContext0.instanceNumber, equalTo(0));
final Coordinate motorTube0Location = motorTubeContext0.getLocation();
assertEquals(motorTube0Location.x, 1.214, EPSILON);
assertEquals(motorTube0Location.y, -0.062, EPSILON);
assertEquals(motorTube0Location.z, -0.015, EPSILON);
final InstanceContext motorTubeContext1 = mmtContextList.get(5);
assertThat(motorTubeContext1.component.getClass(), equalTo(InnerTube.class));
assertThat(motorTubeContext1.instanceNumber, equalTo(1));
final Coordinate motorTube1Location = motorTubeContext1.getLocation();
assertEquals(motorTube1Location.x, 1.214, EPSILON);
assertEquals(motorTube1Location.y, -0.092, EPSILON);
assertEquals(motorTube1Location.z, -0.015, EPSILON);
final InstanceContext motorTubeContext2 = mmtContextList.get(6);
assertThat(motorTubeContext2.component.getClass(), equalTo(InnerTube.class));
assertThat(motorTubeContext2.instanceNumber, equalTo(2));
final Coordinate motorTube2Location = motorTubeContext2.getLocation();
assertEquals(motorTube2Location.x, 1.214, EPSILON);
assertEquals(motorTube2Location.y, -0.092, EPSILON);
assertEquals(motorTube2Location.z, 0.015, EPSILON);
final InstanceContext motorTubeContext3 = mmtContextList.get(7);
assertThat(motorTubeContext3.component.getClass(), equalTo(InnerTube.class));
assertThat(motorTubeContext3.instanceNumber, equalTo(3));
final Coordinate motorTube3Location = motorTubeContext3.getLocation();
assertEquals(motorTube3Location.x, 1.214, EPSILON);
assertEquals(motorTube3Location.y, -0.062, EPSILON);
assertEquals(motorTube3Location.z, 0.015, EPSILON);
}{ // Booster::Fins::Instances ( x2 x3)
final FinSet fins = (FinSet)boosterBody.getChild(1);
final List<InstanceContext> finContextList = instances.getInstanceContexts(fins);
assertEquals(6, finContextList.size());
final InstanceContext boosterFinContext0 = finContextList.get(3);
assertThat(boosterFinContext0.component.getClass(), equalTo(TrapezoidFinSet.class));
assertThat(boosterFinContext0.instanceNumber, equalTo(0));
final Coordinate boosterFin0Location = boosterFinContext0.getLocation();
assertEquals(boosterFin0Location.x, 1.044, EPSILON);
assertEquals(boosterFin0Location.y, -0.104223611, EPSILON);
assertEquals(boosterFin0Location.z, -0.027223611, EPSILON);
final InstanceContext boosterFinContext1 = finContextList.get(4);
assertThat(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);
final InstanceContext boosterFinContext2 = finContextList.get(5);
assertThat(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);
}
}
}
}
}

View File

@ -7,7 +7,6 @@ import static org.junit.Assert.assertThat;
import org.junit.Test;
import net.sf.openrocket.aerodynamics.FlightConditions;
import net.sf.openrocket.rocketcomponent.position.AngleMethod;
import net.sf.openrocket.rocketcomponent.position.RadiusMethod;
import net.sf.openrocket.util.Coordinate;
@ -35,12 +34,12 @@ public class RocketTest extends BaseTestCase {
FlightConfigurationId fcid4 = config4.getId();
assertThat("fcids should match: ", config1.getId().key, equalTo(fcid4.key));
assertThat("Configurations should be different: "+config1.toDebug()+"=?="+config4.toDebug(), config1.instanceNumber, not( config4.instanceNumber));
assertThat("Configurations should be different: "+config1.toDebug()+"=?="+config4.toDebug(), config1.configurationInstanceId, not( config4.configurationInstanceId));
FlightConfiguration config5 = rkt2.getFlightConfiguration(config2.getId());
FlightConfigurationId fcid5 = config5.getId();
assertThat("fcids should match: ", config2.getId(), equalTo(fcid5));
assertThat("Configurations should bef different match: "+config2.toDebug()+"=?="+config5.toDebug(), config2.instanceNumber, not( config5.instanceNumber));
assertThat("Configurations should bef different match: "+config2.toDebug()+"=?="+config5.toDebug(), config2.configurationInstanceId, not( config5.configurationInstanceId));
}
@ -260,7 +259,7 @@ public class RocketTest extends BaseTestCase {
loc = coreBody.getComponentLocations()[0];
assertEquals(coreBody.getName()+" offset is incorrect: ", 0.0, offset.x, EPSILON);
assertEquals(coreBody.getName()+" location is incorrect: ", 0.564, loc.x, EPSILON);
// ====== Booster Set Stage ======
// ====== ====== ======
ParallelStage boosters = (ParallelStage) coreBody.getChild(0);