Add CoordTransform framework + generify exporter instantiation

This commit is contained in:
SiboVG 2023-08-10 06:19:52 +02:00
parent 6d6f5287f3
commit 05624ff35b
16 changed files with 286 additions and 232 deletions

View File

@ -0,0 +1,12 @@
package net.sf.openrocket.file.wavefrontobj;
import de.javagl.obj.FloatTuple;
/**
* Interface for classes that can convert coordinates from the OpenRocket coordinate system to a custom OBJ coordinate system.
*
* @author Sibo Van Gool <sibo.vangool@hotmail.com>
*/
public interface CoordTransform {
FloatTuple convertToOBJCoord(double x, double y, double z);
}

View File

@ -0,0 +1,23 @@
package net.sf.openrocket.file.wavefrontobj;
import de.javagl.obj.FloatTuple;
/**
* Default OpenRocket coordinate system to OBJ coordinate system converter.
* This uses a right-handed coordinate system with if viewed from the side view in OpenRocket, the OBJ axes are:
* - z-axis starting at the bottom of the rocket and pointing to the left (to the rocket tip)
* - side view is in the z-x plane, with the x-axis pointing up
* - y-axis is pointing away from the viewer
*/
public class DefaultCoordTransform implements CoordTransform {
private final double rocketLength;
public DefaultCoordTransform(double rocketLength) {
this.rocketLength = rocketLength;
}
@Override
public FloatTuple convertToOBJCoord(double x, double y, double z) {
return new DefaultFloatTuple((float) y, (float) z, (float) (this.rocketLength - x));
}
}

View File

@ -1,6 +1,7 @@
package net.sf.openrocket.file.wavefrontobj.export;
import de.javagl.obj.ObjWriter;
import net.sf.openrocket.file.wavefrontobj.CoordTransform;
import net.sf.openrocket.file.wavefrontobj.DefaultObj;
import net.sf.openrocket.file.wavefrontobj.ObjUtils;
import net.sf.openrocket.file.wavefrontobj.export.components.BodyTubeExporter;
@ -30,6 +31,7 @@ import java.io.IOException;
import java.io.OutputStream;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
@ -47,13 +49,26 @@ import java.util.Set;
* @author Sibo Van Gool <sibo.vangool@hotmail.com>
*/
public class OBJExporterFactory {
private final List<RocketComponent> components;
private final boolean exportChildren;
private final boolean triangulate;
private final boolean removeOffset;
private final ObjUtils.LevelOfDetail LOD; // Level of detailed used for the export
private final String filePath;
private final CoordTransform transformer;
// The different exporters for each component
private static final Map<Class<? extends RocketComponent>, ExporterFactory<?>> EXPORTER_MAP = Map.of(
BodyTube.class, (ExporterFactory<BodyTube>) BodyTubeExporter::new,
Transition.class, (ExporterFactory<Transition>) TransitionExporter::new,
LaunchLug.class, (ExporterFactory<LaunchLug>) LaunchLugExporter::new,
TubeFinSet.class, (ExporterFactory<TubeFinSet>) TubeFinSetExporter::new,
FinSet.class, (ExporterFactory<FinSet>) FinSetExporter::new,
ThicknessRingComponent.class, (ExporterFactory<ThicknessRingComponent>) ThicknessRingComponentExporter::new,
RadiusRingComponent.class, (ExporterFactory<RadiusRingComponent>) RadiusRingComponentExporter::new,
MassObject.class, (ExporterFactory<MassObject>) MassObjectExporter::new,
RailButton.class, (ExporterFactory<RailButton>) RailButtonExporter::new
);
/**
* Exports a list of rocket components to a Wavefront OBJ file.
@ -63,15 +78,17 @@ public class OBJExporterFactory {
* @param triangulate If true, triangulate all faces
* @param removeOffset If true, remove the offset of the object so it is centered at the origin (but the bottom of the object is at y=0)
* @param LOD Level of detail to use for the export (e.g. '80')
* @param transformer Coordinate system transformer to use to switch from the OpenRocket coordinate system to a custom OBJ coordinate system
* @param filePath Path to the file to export to
*/
public OBJExporterFactory(List<RocketComponent> components, boolean exportChildren, boolean triangulate,
boolean removeOffset, ObjUtils.LevelOfDetail LOD, String filePath) {
boolean removeOffset, ObjUtils.LevelOfDetail LOD, CoordTransform transformer, String filePath) {
this.components = components;
this.exportChildren = exportChildren;
this.triangulate = triangulate;
this.removeOffset = removeOffset;
this.LOD = LOD;
this.transformer = transformer;
this.filePath = filePath;
}
@ -81,11 +98,12 @@ public class OBJExporterFactory {
* @param exportChildren If true, export all children of the components as well
* @param triangulate If true, triangulate all faces
* @param removeOffset If true, remove the offset of the object so it is centered at the origin (but the bottom of the object is at y=0)
* @param transformer Coordinate system transformer to use to switch from the OpenRocket coordinate system to a custom OBJ coordinate system
* @param filePath Path to the file to export to
*/
public OBJExporterFactory(List<RocketComponent> components, boolean exportChildren, boolean triangulate,
boolean removeOffset, String filePath) {
this(components, exportChildren, triangulate, removeOffset, ObjUtils.LevelOfDetail.NORMAL, filePath);
boolean removeOffset, CoordTransform transformer, String filePath) {
this(components, exportChildren, triangulate, removeOffset, ObjUtils.LevelOfDetail.NORMAL, transformer, filePath);
}
/**
@ -103,37 +121,13 @@ public class OBJExporterFactory {
int idx = 1;
for (RocketComponent component : componentsToExport) {
final RocketComponentExporter exporter;
String groupName = component.getName() + "_" + idx; // Add index to make the name unique
if (component instanceof BodyTube) {
exporter = new BodyTubeExporter(obj, (BodyTube) component, groupName, this.LOD);
} else if (component instanceof Transition) {
exporter = new TransitionExporter(obj, (Transition) component, groupName, this.LOD);
}else if (component instanceof LaunchLug) {
exporter = new LaunchLugExporter(obj, (LaunchLug) component, groupName, this.LOD);
} else if (component instanceof TubeFinSet) {
exporter = new TubeFinSetExporter(obj, (TubeFinSet) component, groupName, this.LOD);
} else if (component instanceof FinSet) {
exporter = new FinSetExporter(obj, (FinSet) component, groupName, this.LOD);
} else if (component instanceof ThicknessRingComponent) {
exporter = new ThicknessRingComponentExporter(obj, (ThicknessRingComponent) component, groupName, this.LOD);
} else if (component instanceof RadiusRingComponent) {
exporter = new RadiusRingComponentExporter(obj, (RadiusRingComponent) component, groupName, this.LOD);
} else if (component instanceof MassObject) {
exporter = new MassObjectExporter(obj, (MassObject) component, groupName, this.LOD);
} else if (component instanceof RailButton) {
exporter = new RailButtonExporter(obj, (RailButton) component, groupName, this.LOD);
} else if (component instanceof ComponentAssembly) {
// Do nothing, component assembly instances are handled by the individual rocket component exporters
// by using getComponentLocations()
if (component instanceof ComponentAssembly) {
continue;
} else {
throw new IllegalArgumentException("Unknown component type");
}
exporter.addToObj();
String groupName = component.getName() + "_" + idx;
handleComponent(obj, component, groupName, this.LOD, this.transformer);
idx++;
}
@ -155,6 +149,30 @@ public class OBJExporterFactory {
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@SuppressWarnings("unchecked") // This is safe because of the structure we set up.
private <T extends RocketComponent> void handleComponent(DefaultObj obj, T component, String groupName,
ObjUtils.LevelOfDetail LOD, CoordTransform transformer) {
ExporterFactory<T> factory = null;
Class<?> currentClass = component.getClass();
// Need to iterate over superclasses to find the correct exporter (otherwise e.g. a NoseCone would not work for the TransitionExporter)
while (RocketComponent.class.isAssignableFrom(currentClass) && factory == null) {
factory = (ExporterFactory<T>) EXPORTER_MAP.get(currentClass);
currentClass = currentClass.getSuperclass();
}
if (factory == null) {
throw new IllegalArgumentException("Unsupported component type: " + component.getClass().getName());
}
final RocketComponentExporter<T> exporter = factory.create(obj, component, groupName, LOD, transformer);
exporter.addToObj();
}
interface ExporterFactory<T extends RocketComponent> {
RocketComponentExporter<T> create(DefaultObj obj, T component, String groupName,
ObjUtils.LevelOfDetail LOD, CoordTransform transformer);
}
}

View File

@ -1,5 +1,6 @@
package net.sf.openrocket.file.wavefrontobj.export.components;
import net.sf.openrocket.file.wavefrontobj.CoordTransform;
import net.sf.openrocket.file.wavefrontobj.DefaultObj;
import net.sf.openrocket.file.wavefrontobj.ObjUtils;
import net.sf.openrocket.file.wavefrontobj.export.shapes.CylinderExporter;
@ -7,28 +8,27 @@ import net.sf.openrocket.file.wavefrontobj.export.shapes.TubeExporter;
import net.sf.openrocket.rocketcomponent.BodyTube;
import net.sf.openrocket.util.Coordinate;
public class BodyTubeExporter extends RocketComponentExporter {
public BodyTubeExporter(DefaultObj obj, BodyTube component, String groupName, ObjUtils.LevelOfDetail LOD) {
super(obj, component, groupName, LOD);
public class BodyTubeExporter extends RocketComponentExporter<BodyTube> {
public BodyTubeExporter(DefaultObj obj, BodyTube component, String groupName,
ObjUtils.LevelOfDetail LOD, CoordTransform transformer) {
super(obj, component, groupName, LOD, transformer);
}
@Override
public void addToObj() {
final BodyTube bodyTube = (BodyTube) component;
final float outerRadius = (float) bodyTube.getOuterRadius();
final float innerRadius = (float) bodyTube.getInnerRadius();
final float length = (float) bodyTube.getLength();
final boolean isFilled = bodyTube.isFilled();
final Coordinate[] locations = bodyTube.getComponentLocations();
final float outerRadius = (float) component.getOuterRadius();
final float innerRadius = (float) component.getInnerRadius();
final float length = (float) component.getLength();
final boolean isFilled = component.isFilled();
final Coordinate[] locations = component.getComponentLocations();
// Generate the mesh
for (Coordinate location : locations) {
generateMesh(bodyTube, outerRadius, innerRadius, length, isFilled, location);
generateMesh(outerRadius, innerRadius, length, isFilled, location);
}
}
private void generateMesh(BodyTube bodyTube, float outerRadius, float innerRadius, float length, boolean isFilled,
private void generateMesh(float outerRadius, float innerRadius, float length, boolean isFilled,
Coordinate location) {
int startIdx = obj.getNumVertices();
@ -45,6 +45,6 @@ public class BodyTubeExporter extends RocketComponentExporter {
int endIdx = Math.max(obj.getNumVertices() - 1, startIdx); // Clamp in case no vertices were added
// Translate the mesh to the position in the rocket
ObjUtils.translateVerticesFromComponentLocation(obj, bodyTube, startIdx, endIdx, location, -length);
ObjUtils.translateVerticesFromComponentLocation(obj, component, startIdx, endIdx, location, -length);
}
}

View File

@ -1,5 +1,6 @@
package net.sf.openrocket.file.wavefrontobj.export.components;
import net.sf.openrocket.file.wavefrontobj.CoordTransform;
import net.sf.openrocket.file.wavefrontobj.DefaultObj;
import net.sf.openrocket.file.wavefrontobj.ObjUtils;
import net.sf.openrocket.file.wavefrontobj.export.shapes.PolygonExporter;
@ -9,30 +10,28 @@ import net.sf.openrocket.util.Coordinate;
import java.util.ArrayList;
import java.util.List;
public class FinSetExporter extends RocketComponentExporter {
public FinSetExporter(DefaultObj obj, FinSet component, String groupName, ObjUtils.LevelOfDetail LOD) {
super(obj, component, groupName, LOD);
public class FinSetExporter extends RocketComponentExporter<FinSet> {
public FinSetExporter(DefaultObj obj, FinSet component, String groupName,
ObjUtils.LevelOfDetail LOD, CoordTransform transformer) {
super(obj, component, groupName, LOD, transformer);
}
@Override
public void addToObj() {
final FinSet finSet = (FinSet) component;
obj.setActiveGroupNames(groupName);
final Coordinate[] points = finSet.getFinPointsWithRoot();
final Coordinate[] tabPoints = finSet.getTabPoints();
final Coordinate[] points = component.getFinPointsWithRoot();
final Coordinate[] tabPoints = component.getTabPoints();
final Coordinate[] tabPointsReversed = new Coordinate[tabPoints.length];
for (int i = 0; i < tabPoints.length; i++) {
tabPointsReversed[i] = tabPoints[tabPoints.length - i - 1];
}
final FloatPoints floatPoints = getPointsAsFloat(points);
final FloatPoints floatTabPoints = getPointsAsFloat(tabPointsReversed);
final float thickness = (float) finSet.getThickness();
final double rocketLength = finSet.getRocket().getLength();
boolean hasTabs = finSet.getTabLength() > 0 && finSet.getTabHeight() > 0;
final Coordinate[] locations = finSet.getComponentLocations();
final double[] angles = finSet.getComponentAngles();
final float thickness = (float) component.getThickness();
boolean hasTabs = component.getTabLength() > 0 && component.getTabHeight() > 0;
final Coordinate[] locations = component.getComponentLocations();
final double[] angles = component.getComponentAngles();
if (locations.length != angles.length) {
throw new IllegalArgumentException("Number of locations and angles must match");
@ -40,11 +39,11 @@ public class FinSetExporter extends RocketComponentExporter {
// Generate the fin meshes
for (int i = 0; i < locations.length; i++) {
generateMesh(finSet,floatPoints, floatTabPoints, thickness, hasTabs, locations[i], angles[i]);
generateMesh(floatPoints, floatTabPoints, thickness, hasTabs, locations[i], angles[i]);
}
}
private void generateMesh(FinSet finSet, FloatPoints floatPoints, FloatPoints floatTabPoints, float thickness,
private void generateMesh(FloatPoints floatPoints, FloatPoints floatTabPoints, float thickness,
boolean hasTabs, Coordinate location, double angle) {
// Generate the mesh
final int startIdx = obj.getNumVertices();
@ -64,9 +63,9 @@ public class FinSetExporter extends RocketComponentExporter {
int normalsEndIdx = Math.max(obj.getNumNormals() - 1, normalsStartIdx); // Clamp in case no normals were added
// First rotate for the cant angle
final float cantAngle = (float) - finSet.getCantAngle();
final float cantAngle = (float) -component.getCantAngle();
ObjUtils.rotateVertices(obj, startIdx, endIdx, normalsStartIdx, normalsEndIdx,
cantAngle, 0, 0, 0, (float) - finSet.getLength(), 0);
cantAngle, 0, 0, 0, (float) -component.getLength(), 0);
// Then do the axial rotation
final float axialRot = (float) angle;
@ -74,7 +73,7 @@ public class FinSetExporter extends RocketComponentExporter {
0, axialRot, 0, 0, 0, 0);
// Translate the mesh to the position in the rocket
ObjUtils.translateVerticesFromComponentLocation(obj, finSet, startIdx, endIdx, location, 0);
ObjUtils.translateVerticesFromComponentLocation(obj, component, startIdx, endIdx, location, 0);
}
/**

View File

@ -1,5 +1,6 @@
package net.sf.openrocket.file.wavefrontobj.export.components;
import net.sf.openrocket.file.wavefrontobj.CoordTransform;
import net.sf.openrocket.file.wavefrontobj.DefaultObj;
import net.sf.openrocket.file.wavefrontobj.ObjUtils;
import net.sf.openrocket.file.wavefrontobj.export.shapes.CylinderExporter;
@ -7,27 +8,26 @@ import net.sf.openrocket.file.wavefrontobj.export.shapes.TubeExporter;
import net.sf.openrocket.rocketcomponent.LaunchLug;
import net.sf.openrocket.util.Coordinate;
public class LaunchLugExporter extends RocketComponentExporter {
public LaunchLugExporter(DefaultObj obj, LaunchLug component, String groupName, ObjUtils.LevelOfDetail LOD) {
super(obj, component, groupName, LOD);
public class LaunchLugExporter extends RocketComponentExporter<LaunchLug> {
public LaunchLugExporter(DefaultObj obj, LaunchLug component, String groupName,
ObjUtils.LevelOfDetail LOD, CoordTransform transformer) {
super(obj, component, groupName, LOD, transformer);
}
@Override
public void addToObj() {
final LaunchLug lug = (LaunchLug) component;
final Coordinate[] locations = lug.getComponentLocations();
final float outerRadius = (float) lug.getOuterRadius();
final float innerRadius = (float) lug.getInnerRadius();
final float length = (float) lug.getLength();
final Coordinate[] locations = component.getComponentLocations();
final float outerRadius = (float) component.getOuterRadius();
final float innerRadius = (float) component.getInnerRadius();
final float length = (float) component.getLength();
// Generate the mesh
for (Coordinate location : locations) {
generateMesh(lug, outerRadius, innerRadius, length, location);
generateMesh(outerRadius, innerRadius, length, location);
}
}
private void generateMesh(LaunchLug lug, float outerRadius, float innerRadius, float length, Coordinate location) {
private void generateMesh(float outerRadius, float innerRadius, float length, Coordinate location) {
int startIdx = obj.getNumVertices();
// Generate the instance mesh
@ -44,6 +44,6 @@ public class LaunchLugExporter extends RocketComponentExporter {
int endIdx = Math.max(obj.getNumVertices() - 1, startIdx); // Clamp in case no vertices were added
// Translate the mesh to the position in the rocket
ObjUtils.translateVerticesFromComponentLocation(obj, lug, startIdx, endIdx, location, -length);
ObjUtils.translateVerticesFromComponentLocation(obj, component, startIdx, endIdx, location, -length);
}
}

View File

@ -1,5 +1,6 @@
package net.sf.openrocket.file.wavefrontobj.export.components;
import net.sf.openrocket.file.wavefrontobj.CoordTransform;
import net.sf.openrocket.file.wavefrontobj.DefaultObj;
import net.sf.openrocket.file.wavefrontobj.DefaultObjFace;
import net.sf.openrocket.file.wavefrontobj.ObjUtils;
@ -7,32 +8,31 @@ import net.sf.openrocket.rocketcomponent.MassObject;
import net.sf.openrocket.util.Coordinate;
import net.sf.openrocket.util.RocketComponentUtils;
public class MassObjectExporter extends RocketComponentExporter {
public MassObjectExporter(DefaultObj obj, MassObject component, String groupName, ObjUtils.LevelOfDetail LOD) {
super(obj, component, groupName, LOD);
public class MassObjectExporter extends RocketComponentExporter<MassObject> {
public MassObjectExporter(DefaultObj obj, MassObject component, String groupName,
ObjUtils.LevelOfDetail LOD, CoordTransform transformer) {
super(obj, component, groupName, LOD, transformer);
}
@Override
public void addToObj() {
final MassObject massObject = (MassObject) component;
obj.setActiveGroupNames(groupName);
final Coordinate[] locations = massObject.getComponentLocations();
final Coordinate[] locations = component.getComponentLocations();
final int numSides = LOD.getValue() / 2;
final int numStacks = LOD.getValue() / 2;
// Generate the mesh
for (Coordinate location : locations) {
generateMesh(massObject, numSides, numStacks, location);
generateMesh(numSides, numStacks, location);
}
}
private void generateMesh(MassObject massObject, int numSides, int numStacks, Coordinate location) {
private void generateMesh(int numSides, int numStacks, Coordinate location) {
// Other meshes may have been added to the obj, so we need to keep track of the starting indices
int startIdx = obj.getNumVertices();
int normalsStartIdx = obj.getNumNormals();
double dy = massObject.getLength() / numStacks;
double dy = component.getLength() / numStacks;
double da = 2.0f * Math.PI / numSides;
// Generate vertices and normals
@ -47,21 +47,21 @@ public class MassObjectExporter extends RocketComponentExporter {
// Add a vertex for each side
for (int i = 0; i < numSides; i++) {
double angle = i * da;
double r = RocketComponentUtils.getMassObjectRadius(massObject, y);
double r = RocketComponentUtils.getMassObjectRadius(component, y);
float x = (float) (r * Math.cos(angle));
float z = (float) (r * Math.sin(angle));
obj.addVertex(x, (float) y, z);
// Add normals
if (Double.compare(r, massObject.getRadius()) == 0) {
if (Double.compare(r, component.getRadius()) == 0) {
obj.addNormal(x, 0, z);
} else {
final double yCenter;
if (j <= numStacks/2) {
yCenter = RocketComponentUtils.getMassObjectArcHeight(massObject);
yCenter = RocketComponentUtils.getMassObjectArcHeight(component);
} else {
yCenter = massObject.getLength() - RocketComponentUtils.getMassObjectArcHeight(massObject);
yCenter = component.getLength() - RocketComponentUtils.getMassObjectArcHeight(component);
}
obj.addNormal(x, (float) (y - yCenter), z); // For smooth shading
}
@ -133,8 +133,8 @@ public class MassObjectExporter extends RocketComponentExporter {
// Translate the mesh to the position in the rocket
// We will create an offset location that has the same effect as the axial rotation of the mass object
Coordinate offsetLocation = getOffsetLocation(massObject, location);
ObjUtils.translateVerticesFromComponentLocation(obj, massObject, startIdx, endIdx, offsetLocation, -massObject.getLength());
Coordinate offsetLocation = getOffsetLocation(component, location);
ObjUtils.translateVerticesFromComponentLocation(obj, component, startIdx, endIdx, offsetLocation, -component.getLength());
}
private static Coordinate getOffsetLocation(MassObject massObject, Coordinate location) {

View File

@ -1,5 +1,6 @@
package net.sf.openrocket.file.wavefrontobj.export.components;
import net.sf.openrocket.file.wavefrontobj.CoordTransform;
import net.sf.openrocket.file.wavefrontobj.DefaultObj;
import net.sf.openrocket.file.wavefrontobj.ObjUtils;
import net.sf.openrocket.file.wavefrontobj.export.shapes.CylinderExporter;
@ -7,20 +8,18 @@ import net.sf.openrocket.file.wavefrontobj.export.shapes.TubeExporter;
import net.sf.openrocket.rocketcomponent.RadiusRingComponent;
import net.sf.openrocket.util.Coordinate;
public class RadiusRingComponentExporter extends RocketComponentExporter {
public RadiusRingComponentExporter(DefaultObj obj, RadiusRingComponent component, String groupName, ObjUtils.LevelOfDetail LOD) {
super(obj, component, groupName, LOD);
public class RadiusRingComponentExporter extends RocketComponentExporter<RadiusRingComponent> {
public RadiusRingComponentExporter(DefaultObj obj, RadiusRingComponent component, String groupName,
ObjUtils.LevelOfDetail LOD, CoordTransform transformer) {
super(obj, component, groupName, LOD, transformer);
}
@Override
public void addToObj() {
final RadiusRingComponent radiusRing = (RadiusRingComponent) component;
float outerRadius = (float) radiusRing.getOuterRadius();
float innerRadius = (float) radiusRing.getInnerRadius();
float thickness = (float) radiusRing.getThickness();
final double rocketLength = radiusRing.getRocket().getLength();
final Coordinate[] locations = radiusRing.getComponentLocations();
float outerRadius = (float) component.getOuterRadius();
float innerRadius = (float) component.getInnerRadius();
float thickness = (float) component.getThickness();
final Coordinate[] locations = component.getComponentLocations();
// Generate the mesh
for (Coordinate location : locations) {

View File

@ -1,18 +1,18 @@
package net.sf.openrocket.file.wavefrontobj.export.components;
import net.sf.openrocket.file.wavefrontobj.CoordTransform;
import net.sf.openrocket.file.wavefrontobj.DefaultObj;
import net.sf.openrocket.file.wavefrontobj.DefaultObjFace;
import net.sf.openrocket.file.wavefrontobj.ObjUtils;
import net.sf.openrocket.file.wavefrontobj.export.shapes.CylinderExporter;
import net.sf.openrocket.file.wavefrontobj.export.shapes.DiskExporter;
import net.sf.openrocket.rocketcomponent.RailButton;
import net.sf.openrocket.rocketcomponent.RocketComponent;
import net.sf.openrocket.util.Coordinate;
import java.util.ArrayList;
import java.util.List;
public class RailButtonExporter extends RocketComponentExporter {
public class RailButtonExporter extends RocketComponentExporter<RailButton> {
/**
* Wavefront OBJ exporter for a rail button.
*
@ -21,24 +21,23 @@ public class RailButtonExporter extends RocketComponentExporter {
* @param groupName The name of the group to export to
* @param LOD Level of detail to use for the export (e.g. '80')
*/
public RailButtonExporter(DefaultObj obj, RailButton component, String groupName, ObjUtils.LevelOfDetail LOD) {
super(obj, component, groupName, LOD);
public RailButtonExporter(DefaultObj obj, RailButton component, String groupName,
ObjUtils.LevelOfDetail LOD, CoordTransform transformer) {
super(obj, component, groupName, LOD, transformer);
}
@Override
public void addToObj() {
final RailButton railButton = (RailButton) component;
obj.setActiveGroupNames(groupName);
final float outerRadius = (float) railButton.getOuterDiameter() / 2;
final float innerRadius = (float) railButton.getInnerDiameter() / 2;
final float baseHeight = (float) railButton.getBaseHeight();
final float innerHeight = (float) railButton.getInnerHeight();
final float flangeHeight = (float) railButton.getFlangeHeight();
final float screwHeight = (float) railButton.getScrewHeight();
final Coordinate[] locations = railButton.getComponentLocations();
final double[] angles = railButton.getComponentAngles();
final float outerRadius = (float) component.getOuterDiameter() / 2;
final float innerRadius = (float) component.getInnerDiameter() / 2;
final float baseHeight = (float) component.getBaseHeight();
final float innerHeight = (float) component.getInnerHeight();
final float flangeHeight = (float) component.getFlangeHeight();
final float screwHeight = (float) component.getScrewHeight();
final Coordinate[] locations = component.getComponentLocations();
final double[] angles = component.getComponentAngles();
// Generate the mesh
for (int i = 0; i < locations.length; i++) {

View File

@ -1,5 +1,6 @@
package net.sf.openrocket.file.wavefrontobj.export.components;
import net.sf.openrocket.file.wavefrontobj.CoordTransform;
import net.sf.openrocket.file.wavefrontobj.DefaultObj;
import net.sf.openrocket.file.wavefrontobj.ObjUtils;
import net.sf.openrocket.file.wavefrontobj.export.OBJExporterFactory;
@ -11,11 +12,12 @@ import net.sf.openrocket.rocketcomponent.RocketComponent;
*
* @author Sibo Van Gool <sibo.vangool@hotmail.com>
*/
public abstract class RocketComponentExporter {
public abstract class RocketComponentExporter<T extends RocketComponent> {
protected final DefaultObj obj;
protected final RocketComponent component;
protected final T component;
protected final String groupName;
protected final ObjUtils.LevelOfDetail LOD;
protected final CoordTransform transformer;
/**
* Wavefront OBJ exporter for a rocket component.
@ -23,12 +25,15 @@ public abstract class RocketComponentExporter {
* @param component The component to export
* @param groupName The name of the group to export to
* @param LOD Level of detail to use for the export (e.g. '80')
* @param transformer Coordinate system transformer to use to switch from the OpenRocket coordinate system to a custom OBJ coordinate system
*/
public RocketComponentExporter(DefaultObj obj, RocketComponent component, String groupName, ObjUtils.LevelOfDetail LOD) {
public RocketComponentExporter(DefaultObj obj, T component, String groupName,
ObjUtils.LevelOfDetail LOD, CoordTransform transformer) {
this.obj = obj;
this.component = component;
this.groupName = groupName;
this.LOD = LOD;
this.transformer = transformer;
}
public abstract void addToObj();

View File

@ -1,5 +1,6 @@
package net.sf.openrocket.file.wavefrontobj.export.components;
import net.sf.openrocket.file.wavefrontobj.CoordTransform;
import net.sf.openrocket.file.wavefrontobj.DefaultObj;
import net.sf.openrocket.file.wavefrontobj.ObjUtils;
import net.sf.openrocket.file.wavefrontobj.export.shapes.CylinderExporter;
@ -7,19 +8,18 @@ import net.sf.openrocket.file.wavefrontobj.export.shapes.TubeExporter;
import net.sf.openrocket.rocketcomponent.ThicknessRingComponent;
import net.sf.openrocket.util.Coordinate;
public class ThicknessRingComponentExporter extends RocketComponentExporter {
public ThicknessRingComponentExporter(DefaultObj obj, ThicknessRingComponent component, String groupName, ObjUtils.LevelOfDetail LOD) {
super(obj, component, groupName, LOD);
public class ThicknessRingComponentExporter extends RocketComponentExporter<ThicknessRingComponent> {
public ThicknessRingComponentExporter(DefaultObj obj, ThicknessRingComponent component, String groupName,
ObjUtils.LevelOfDetail LOD, CoordTransform transformer) {
super(obj, component, groupName, LOD, transformer);
}
@Override
public void addToObj() {
final ThicknessRingComponent thicknessRing = (ThicknessRingComponent) component;
final float outerRadius = (float) thicknessRing.getOuterRadius();
final float innerRadius = (float) thicknessRing.getInnerRadius();
final float length = (float) thicknessRing.getLength();
final Coordinate[] locations = thicknessRing.getComponentLocations();
final float outerRadius = (float) component.getOuterRadius();
final float innerRadius = (float) component.getInnerRadius();
final float length = (float) component.getLength();
final Coordinate[] locations = component.getComponentLocations();
// Generate the mesh
for (Coordinate location : locations) {

View File

@ -1,5 +1,6 @@
package net.sf.openrocket.file.wavefrontobj.export.components;
import net.sf.openrocket.file.wavefrontobj.CoordTransform;
import net.sf.openrocket.file.wavefrontobj.DefaultObj;
import net.sf.openrocket.file.wavefrontobj.DefaultObjFace;
import net.sf.openrocket.file.wavefrontobj.ObjUtils;
@ -12,7 +13,7 @@ import net.sf.openrocket.util.Coordinate;
import java.util.ArrayList;
import java.util.List;
public class TransitionExporter extends RocketComponentExporter {
public class TransitionExporter extends RocketComponentExporter<Transition> {
private static final double RADIUS_EPSILON = 1e-4;
private final int nrOfSides;
@ -23,34 +24,33 @@ public class TransitionExporter extends RocketComponentExporter {
* @param groupName Name of the group to export to
* @param LOD Level of detail to use for the export
*/
public TransitionExporter(DefaultObj obj, Transition component, String groupName, ObjUtils.LevelOfDetail LOD) {
super(obj, component, groupName, LOD);
public TransitionExporter(DefaultObj obj, Transition component, String groupName,
ObjUtils.LevelOfDetail LOD, CoordTransform transformer) {
super(obj, component, groupName, LOD, transformer);
this.nrOfSides = LOD.getNrOfSides(Math.max(component.getForeRadius(), component.getAftRadius()));
}
@Override
public void addToObj() {
final Transition transition = (Transition) component;
obj.setActiveGroupNames(groupName);
final Coordinate[] locations = transition.getComponentLocations();
final Coordinate[] locations = component.getComponentLocations();
// Generate the mesh
for (Coordinate location : locations) {
generateMesh(transition, location);
generateMesh(location);
}
}
private void generateMesh(Transition transition, Coordinate location) {
private void generateMesh(Coordinate location) {
int startIdx = obj.getNumVertices();
final boolean hasForeShoulder = Double.compare(transition.getForeShoulderRadius(), 0) > 0
&& Double.compare(transition.getForeShoulderLength(), 0) > 0
&& transition.getForeRadius() > 0;
final boolean hasAftShoulder = Double.compare(transition.getAftShoulderRadius(), 0) > 0
&& Double.compare(transition.getAftShoulderLength(), 0) > 0
&& transition.getAftRadius() > 0;
final boolean hasForeShoulder = Double.compare(component.getForeShoulderRadius(), 0) > 0
&& Double.compare(component.getForeShoulderLength(), 0) > 0
&& component.getForeRadius() > 0;
final boolean hasAftShoulder = Double.compare(component.getAftShoulderRadius(), 0) > 0
&& Double.compare(component.getAftShoulderLength(), 0) > 0
&& component.getAftRadius() > 0;
final List<Integer> outsideForeRingVertices = new ArrayList<>();
final List<Integer> outsideAftRingVertices = new ArrayList<>();
@ -58,59 +58,57 @@ public class TransitionExporter extends RocketComponentExporter {
final List<Integer> insideAftRingVertices = new ArrayList<>();
// Check if geometry is a simple cylinder
if (Double.compare(transition.getAftRadius(), transition.getForeRadius()) == 0 ||
transition.getShapeType() == Transition.Shape.CONICAL ||
(transition.getShapeType() == Transition.Shape.OGIVE && transition.getShapeParameter() == 0) ||
(transition.getShapeType() == Transition.Shape.POWER && transition.getShapeParameter() == 1) ||
(transition.getShapeType() == Transition.Shape.PARABOLIC && transition.getShapeParameter() == 0)) {
if (Double.compare(component.getAftRadius(), component.getForeRadius()) == 0 ||
component.getShapeType() == Transition.Shape.CONICAL ||
(component.getShapeType() == Transition.Shape.OGIVE && component.getShapeParameter() == 0) ||
(component.getShapeType() == Transition.Shape.POWER && component.getShapeParameter() == 1) ||
(component.getShapeType() == Transition.Shape.PARABOLIC && component.getShapeParameter() == 0)) {
float outerAft = (float) transition.getAftRadius();
float innerAft = (float) (transition.getAftRadius() - transition.getThickness());
float outerFore = (float) transition.getForeRadius();
float innerFore = (float) (transition.getForeRadius() - transition.getThickness());
float outerAft = (float) component.getAftRadius();
float innerAft = (float) (component.getAftRadius() - component.getThickness());
float outerFore = (float) component.getForeRadius();
float innerFore = (float) (component.getForeRadius() - component.getThickness());
TubeExporter.addTubeMesh(obj, null, outerAft, outerFore, innerAft, innerFore,
(float) transition.getLength(), this.nrOfSides,
(float) component.getLength(), this.nrOfSides,
outsideAftRingVertices, outsideForeRingVertices, insideAftRingVertices, insideForeRingVertices);
}
// Otherwise, use complex geometry
else {
int numStacks = transition.getShapeType() == Transition.Shape.CONICAL ? 4 : this.nrOfSides / 2;
int numStacks = component.getShapeType() == Transition.Shape.CONICAL ? 4 : this.nrOfSides / 2;
// Draw outside
addTransitionMesh(obj, transition, this.nrOfSides, numStacks, 0, true,
addTransitionMesh(this.nrOfSides, numStacks, 0, true,
outsideForeRingVertices, outsideAftRingVertices, hasForeShoulder, hasAftShoulder);
// Draw inside
if (!transition.isFilled()) {
addTransitionMesh(obj, transition, this.nrOfSides, numStacks, -transition.getThickness(), false,
if (!component.isFilled()) {
addTransitionMesh(this.nrOfSides, numStacks, -component.getThickness(), false,
insideForeRingVertices, insideAftRingVertices, hasForeShoulder, hasAftShoulder);
}
// Draw bottom and top face
if (!hasForeShoulder) {
closeFace(obj, transition, outsideForeRingVertices, insideForeRingVertices, true);
closeFace(outsideForeRingVertices, insideForeRingVertices, true);
}
if (!hasAftShoulder) {
closeFace(obj, transition, outsideAftRingVertices, insideAftRingVertices, false);
closeFace(outsideAftRingVertices, insideAftRingVertices, false);
}
}
// Add shoulders
addShoulders(obj, transition, this.nrOfSides, outsideForeRingVertices, outsideAftRingVertices,
addShoulders(this.nrOfSides, outsideForeRingVertices, outsideAftRingVertices,
insideForeRingVertices, insideAftRingVertices, hasForeShoulder, hasAftShoulder);
int endIdx = Math.max(obj.getNumVertices() - 1, startIdx); // Clamp in case no vertices were added
// Translate the mesh to the position in the rocket
ObjUtils.translateVerticesFromComponentLocation(obj, transition, startIdx, endIdx, location, -transition.getLength());
ObjUtils.translateVerticesFromComponentLocation(obj, component, startIdx, endIdx, location, -component.getLength());
}
/**
* Add a transition mesh to the obj.
* @param obj the obj to add the mesh to
* @param transition the transition to draw
* @param numSlices the number of slices to use (= number of vertices in the circumferential direction)
* @param numStacks the number of stacks to use (= number of vertices in the longitudinal direction)
* @param offsetRadius offset radius from the transition radius
@ -118,16 +116,15 @@ public class TransitionExporter extends RocketComponentExporter {
* @param foreRingVertices list of vertices of the fore ring
* @param aftRingVertices list of vertices of the aft ring
*/
private static void addTransitionMesh(DefaultObj obj, Transition transition,
int numSlices, int numStacks, double offsetRadius, boolean isOutside,
private void addTransitionMesh(int numSlices, int numStacks, double offsetRadius, boolean isOutside,
List<Integer> foreRingVertices, List<Integer> aftRingVertices,
boolean hasForeShoulder, boolean hasAftShoulder) {
// Other meshes may have been added to the obj, so we need to keep track of the starting indices
final int startIdx = obj.getNumVertices();
final int normalsStartIdx = obj.getNumNormals();
final double dyBase = transition.getLength() / numStacks; // Base step size in the longitudinal direction
final double actualLength = estimateActualLength(transition, offsetRadius, dyBase);
final double dyBase = component.getLength() / numStacks; // Base step size in the longitudinal direction
final double actualLength = estimateActualLength(offsetRadius, dyBase);
// Generate vertices and normals
float y = 0; // Distance from the aft end
@ -135,19 +132,19 @@ public class TransitionExporter extends RocketComponentExporter {
boolean isForeTip = false; // True if the fore end of the transition is a tip (radius = 0)
boolean isAftTip = false; // True if the aft end of the transition is a tip (radius = 0)
int actualNumStacks = 0; // Number of stacks added, deviates from set point due to reduced step size near the tip (does not include aft/fore tip rings)
while (y <= (float) transition.getLength()) {
while (y <= (float) component.getLength()) {
// When closer to the tip, decrease the step size
double t = Math.min(1, y / actualLength);
if (transition.getForeRadius() < transition.getAftRadius()) {
if (component.getForeRadius() < component.getAftRadius()) {
t = 1 - t;
}
float dy = t < 0.2 ? (float) (dyBase / (5.0 - 20*t)) : (float) dyBase;
float yNext = y + dy;
yNext = Math.min(yNext, (float) transition.getLength());
yNext = Math.min(yNext, (float) component.getLength());
// Calculate the radius at this height
r = Math.max(0, transition.getRadius(transition.getLength()-y) + offsetRadius); // y = 0 is aft and, but this would return the fore radius, so subtract it from the length
double rNext = Math.max(0, transition.getRadius(transition.getLength()-yNext) + offsetRadius);
r = Math.max(0, component.getRadius(component.getLength()-y) + offsetRadius); // y = 0 is aft and, but this would return the fore radius, so subtract it from the length
double rNext = Math.max(0, component.getRadius(component.getLength()-yNext) + offsetRadius);
/*
@ -173,7 +170,7 @@ public class TransitionExporter extends RocketComponentExporter {
} else {
// Case 2: Add a single vertex at the center (= aft tip)
float epsilon = dy / 20;
float yTip = getTipLocation(transition, y, yNext, offsetRadius, epsilon);
float yTip = getTipLocation(y, yNext, offsetRadius, epsilon);
obj.addVertex(0, yTip, 0);
obj.addNormal(0, isOutside ? -1 : 1, 0);
isAftTip = true;
@ -191,8 +188,8 @@ public class TransitionExporter extends RocketComponentExporter {
// because this is where the shoulder base will be
float yClamped = y;
if (!isOutside) {
final double yForeShoulder = transition.getLength() - transition.getForeShoulderThickness();
final double yAftShoulder = transition.getAftShoulderThickness();
final double yForeShoulder = component.getLength() - component.getForeShoulderThickness();
final double yAftShoulder = component.getAftShoulderThickness();
if (hasForeShoulder) {
if (y < yForeShoulder) {
// If the current ring is before the fore shoulder ring and the next ring is after, clamp the
@ -218,10 +215,10 @@ public class TransitionExporter extends RocketComponentExporter {
}
}
if (Double.compare(rNext, RADIUS_EPSILON) <= 0 && Double.compare(yClamped, transition.getLength()) != 0) {
if (Double.compare(rNext, RADIUS_EPSILON) <= 0 && Double.compare(yClamped, component.getLength()) != 0) {
// Case 3: Add a single vertex at the center at the next y position (= fore tip)
float epsilon = dy / 20;
float yTip = getTipLocation(transition, yClamped, yNext, offsetRadius, epsilon);
float yTip = getTipLocation(yClamped, yNext, offsetRadius, epsilon);
obj.addVertex(0, yTip, 0);
obj.addNormal(0, isOutside ? 1 : -1, 0);
isForeTip = true;
@ -233,16 +230,16 @@ public class TransitionExporter extends RocketComponentExporter {
} else {
// Check on which ring we are
boolean isAftRing = actualNumStacks == 0 && aftRingVertices.isEmpty();
boolean isForeRing = Double.compare(yClamped, (float) transition.getLength()) == 0;
boolean isForeRing = Double.compare(yClamped, (float) component.getLength()) == 0;
// Case 5: Add normal vertices
addQuadVertices(obj, numSlices, foreRingVertices, aftRingVertices, r, rNext, yClamped, isAftRing, isForeRing, isOutside);
addQuadVertices(numSlices, foreRingVertices, aftRingVertices, r, rNext, yClamped, isAftRing, isForeRing, isOutside);
actualNumStacks++;
}
}
// If we're at the fore end, stop
if (Float.compare(y, (float) transition.getLength()) == 0) {
if (Float.compare(y, (float) component.getLength()) == 0) {
break;
}
@ -251,16 +248,16 @@ public class TransitionExporter extends RocketComponentExporter {
// Create aft/fore tip faces
if (isAftTip || isForeTip) {
addTipFaces(obj, numSlices, isOutside, isAftTip, startIdx, normalsStartIdx);
addTipFaces(numSlices, isOutside, isAftTip, startIdx, normalsStartIdx);
}
// Create regular faces
int corrVStartIdx = isAftTip ? startIdx + 1 : startIdx;
int corrNStartIdx = isAftTip ? normalsStartIdx + 1 : normalsStartIdx;
addQuadFaces(obj, numSlices, actualNumStacks, corrVStartIdx, corrNStartIdx, isOutside);
addQuadFaces(numSlices, actualNumStacks, corrVStartIdx, corrNStartIdx, isOutside);
}
private static void addTipFaces(DefaultObj obj, int numSlices, boolean isOutside, boolean isAftTip, int startIdx, int normalsStartIdx) {
private void addTipFaces(int numSlices, boolean isOutside, boolean isAftTip, int startIdx, int normalsStartIdx) {
final int lastIdx = obj.getNumVertices() - 1;
for (int i = 0; i < numSlices; i++) {
int nextIdx = (i + 1) % numSlices;
@ -305,7 +302,7 @@ public class TransitionExporter extends RocketComponentExporter {
}
}
private static void addQuadVertices(DefaultObj obj, int numSlices, List<Integer> foreRingVertices, List<Integer> aftRingVertices,
private void addQuadVertices(int numSlices, List<Integer> foreRingVertices, List<Integer> aftRingVertices,
double r, double rNext, float y, boolean isAftRing, boolean isForeRing, boolean isOutside) {
for (int i = 0; i < numSlices; i++) {
double angle = 2 * Math.PI * i / numSlices;
@ -330,7 +327,7 @@ public class TransitionExporter extends RocketComponentExporter {
}
}
private static void addQuadFaces(DefaultObj obj, int numSlices, int numStacks, int startIdx, int normalsStartIdx, boolean isOutside) {
private void addQuadFaces(int numSlices, int numStacks, int startIdx, int normalsStartIdx, boolean isOutside) {
for (int i = 0; i < numStacks - 1; i++) {
for (int j = 0; j < numSlices; j++) {
final int nextIdx = (j + 1) % numSlices;
@ -353,32 +350,30 @@ public class TransitionExporter extends RocketComponentExporter {
}
}
private static void addShoulders(DefaultObj obj, Transition transition, int nrOfSides,
List<Integer> outsideForeRingVertices, List<Integer> outsideAftRingVertices,
List<Integer> insideForeRingVertices, List<Integer> insideAftRingVertices,
boolean hasForeShoulder, boolean hasAftShoulder) {
final float foreShoulderRadius = (float) transition.getForeShoulderRadius();
final float aftShoulderRadius = (float) transition.getAftShoulderRadius();
final float foreShoulderLength = (float) transition.getForeShoulderLength();
final float aftShoulderLength = (float) transition.getAftShoulderLength();
final float foreShoulderThickness = (float) transition.getForeShoulderThickness();
final float aftShoulderThickness = (float) transition.getAftShoulderThickness();
final boolean foreShoulderCapped = transition.isForeShoulderCapped();
final boolean aftShoulderCapped = transition.isAftShoulderCapped();
private void addShoulders(int nrOfSides, List<Integer> outsideForeRingVertices, List<Integer> outsideAftRingVertices,
List<Integer> insideForeRingVertices, List<Integer> insideAftRingVertices,
boolean hasForeShoulder, boolean hasAftShoulder) {
final float foreShoulderRadius = (float) component.getForeShoulderRadius();
final float aftShoulderRadius = (float) component.getAftShoulderRadius();
final float foreShoulderLength = (float) component.getForeShoulderLength();
final float aftShoulderLength = (float) component.getAftShoulderLength();
final float foreShoulderThickness = (float) component.getForeShoulderThickness();
final float aftShoulderThickness = (float) component.getAftShoulderThickness();
final boolean foreShoulderCapped = component.isForeShoulderCapped();
final boolean aftShoulderCapped = component.isAftShoulderCapped();
if (hasForeShoulder) {
addShoulder(obj, transition, foreShoulderRadius, foreShoulderLength, foreShoulderThickness, foreShoulderCapped,
addShoulder(foreShoulderRadius, foreShoulderLength, foreShoulderThickness, foreShoulderCapped,
true, nrOfSides, outsideForeRingVertices, insideForeRingVertices);
}
if (hasAftShoulder) {
addShoulder(obj, transition, aftShoulderRadius, aftShoulderLength, aftShoulderThickness, aftShoulderCapped,
addShoulder(aftShoulderRadius, aftShoulderLength, aftShoulderThickness, aftShoulderCapped,
false, nrOfSides, outsideAftRingVertices, insideAftRingVertices);
}
}
private static void addShoulder(DefaultObj obj, Transition transition, float shoulderRadius, float shoulderLength,
float shoulderThickness, boolean isCapped, boolean isForeSide, int nrOfSides,
List<Integer> outerRingVertices, List<Integer> innerRingVertices) {
private void addShoulder(float shoulderRadius, float shoulderLength, float shoulderThickness, boolean isCapped,
boolean isForeSide, int nrOfSides, List<Integer> outerRingVertices, List<Integer> innerRingVertices) {
final float innerCylinderRadius = isCapped ? 0 : shoulderRadius - shoulderThickness;
final List<Integer> outerCylinderBottomVertices = new ArrayList<>();
final List<Integer> outerCylinderTopVertices = new ArrayList<>();
@ -417,7 +412,7 @@ public class TransitionExporter extends RocketComponentExporter {
endIdx = Math.max(obj.getNumVertices() - 1, startIdx);
// Translate the outer cylinder to the correct position
float dy = isForeSide ? (float) transition.getLength() : -shoulderLength;
float dy = isForeSide ? (float) component.getLength() : -shoulderLength;
ObjUtils.translateVertices(obj, startIdx, endIdx, 0, dy, 0);
// Generate inner cylinder (no. 7)
@ -428,7 +423,7 @@ public class TransitionExporter extends RocketComponentExporter {
endIdx = Math.max(obj.getNumVertices() - 1, startIdx);
// Translate the outer cylinder to the correct position
dy = isForeSide ? (float) transition.getLength() - shoulderThickness : -shoulderLength;
dy = isForeSide ? (float) component.getLength() - shoulderThickness : -shoulderLength;
ObjUtils.translateVertices(obj, startIdx, endIdx, 0, dy, 0);
}
@ -454,31 +449,29 @@ public class TransitionExporter extends RocketComponentExporter {
}
}
private static void closeFace(DefaultObj obj, Transition transition, List<Integer> outerVertices, List<Integer> innerVertices,
boolean isTopFace) {
boolean filledCap = transition.isFilled() || innerVertices.size() <= 1;
private void closeFace(List<Integer> outerVertices, List<Integer> innerVertices, boolean isTopFace) {
boolean filledCap = component.isFilled() || innerVertices.size() <= 1;
DiskExporter.closeDiskMesh(obj, null, outerVertices, filledCap ? null : innerVertices, isTopFace);
}
/**
* Due to the offsetRadius, the length of the transition to be drawn can be smaller than the actual length of the transition,
* because the offsetRadius causes the mesh to "shrink". This method estimates the length of the transition to be drawn.
* @param transition the transition to estimate the length for
* @param offsetRadius the offset radius to the radius
* @param dyBase the base of the dy
* @return the estimated length of the transition to be drawn
*/
private static float estimateActualLength(Transition transition, double offsetRadius, double dyBase) {
private float estimateActualLength(double offsetRadius, double dyBase) {
if (Double.compare(offsetRadius, 0) >= 0) {
return (float) transition.getLength();
return (float) component.getLength();
}
double y = 0;
final float increment = (float) dyBase / 4;
float actualLength = 0;
while (y < transition.getLength()) {
final double r = transition.getRadius(transition.getLength()-y) + offsetRadius;
while (y < component.getLength()) {
final double r = component.getRadius(component.getLength()-y) + offsetRadius;
if (Double.compare(r, 0) > 0) {
actualLength += increment;
@ -492,23 +485,22 @@ public class TransitionExporter extends RocketComponentExporter {
/**
* Locate the best location for the tip of a transition.
* @param transition the transition to look the tip for
* @param yStart the start position to look for
* @param yEnd the end position to look for
* @param offsetRadius the offset radius to the radius
* @param epsilon the increment to parse the next y location
* @return the best location for the tip
*/
private static float getTipLocation(Transition transition, float yStart, float yEnd, double offsetRadius, float epsilon) {
private float getTipLocation(float yStart, float yEnd, double offsetRadius, float epsilon) {
if (Float.compare(yStart, yEnd) == 0 || Float.compare(epsilon, 0) == 0) {
throw new IllegalArgumentException("Invalid parameters");
}
boolean isStartSmaller = transition.getRadius(transition.getLength()-yStart) < transition.getRadius(transition.getLength()-yEnd);
boolean isStartSmaller = component.getRadius(component.getLength()-yStart) < component.getRadius(component.getLength()-yEnd);
if (isStartSmaller) {
for (float y = yEnd; y >= yStart; y -= epsilon) {
double r = Math.max(0, transition.getRadius(transition.getLength() - y) + offsetRadius);
double r = Math.max(0, component.getRadius(component.getLength() - y) + offsetRadius);
if (Double.compare(r, 0) == 0) {
return y;
}
@ -517,7 +509,7 @@ public class TransitionExporter extends RocketComponentExporter {
return yStart;
} else {
for (float y = yStart; y <= yEnd; y += epsilon) {
double r = Math.max(0, transition.getRadius(transition.getLength() - y) + offsetRadius);
double r = Math.max(0, component.getRadius(component.getLength() - y) + offsetRadius);
if (Double.compare(r, 0) == 0) {
return y;
}

View File

@ -1,28 +1,27 @@
package net.sf.openrocket.file.wavefrontobj.export.components;
import net.sf.openrocket.file.wavefrontobj.CoordTransform;
import net.sf.openrocket.file.wavefrontobj.DefaultObj;
import net.sf.openrocket.file.wavefrontobj.ObjUtils;
import net.sf.openrocket.file.wavefrontobj.export.shapes.TubeExporter;
import net.sf.openrocket.rocketcomponent.TubeFinSet;
import net.sf.openrocket.util.Coordinate;
public class TubeFinSetExporter extends RocketComponentExporter {
public TubeFinSetExporter(DefaultObj obj, TubeFinSet component, String groupName, ObjUtils.LevelOfDetail LOD) {
super(obj, component, groupName, LOD);
public class TubeFinSetExporter extends RocketComponentExporter<TubeFinSet> {
public TubeFinSetExporter(DefaultObj obj, TubeFinSet component, String groupName,
ObjUtils.LevelOfDetail LOD, CoordTransform transformer) {
super(obj, component, groupName, LOD, transformer);
}
@Override
public void addToObj() {
final TubeFinSet tubeFinSet = (TubeFinSet) component;
obj.setActiveGroupNames(groupName);
final float outerRadius = (float) tubeFinSet.getOuterRadius();
final float innerRadius = (float) tubeFinSet.getInnerRadius();
final float length = (float) tubeFinSet.getLength();
final Coordinate[] locations = tubeFinSet.getComponentLocations();
final double[] angles = tubeFinSet.getInstanceAngles();
final double rocketLength = tubeFinSet.getRocket().getLength();
final float outerRadius = (float) component.getOuterRadius();
final float innerRadius = (float) component.getInnerRadius();
final float length = (float) component.getLength();
final Coordinate[] locations = component.getComponentLocations();
final double[] angles = component.getInstanceAngles();
if (locations.length != angles.length) {
throw new IllegalArgumentException("Number of locations and angles must match");
@ -30,11 +29,11 @@ public class TubeFinSetExporter extends RocketComponentExporter {
// Generate the fin meshes
for (int i = 0; i < locations.length; i++) {
generateMesh(outerRadius, innerRadius, length, rocketLength, locations[i], angles[i]);
generateMesh(outerRadius, innerRadius, length, locations[i], angles[i]);
}
}
private void generateMesh(float outerRadius, float innerRadius, float length, double rocketLength, Coordinate location, double angle) {
private void generateMesh(float outerRadius, float innerRadius, float length, Coordinate location, double angle) {
// Create the fin meshes
final int startIdx = obj.getNumVertices();

View File

@ -1,5 +1,6 @@
package net.sf.openrocket.file.wavefrontobj.export.shapes;
import de.javagl.obj.FloatTuple;
import net.sf.openrocket.file.wavefrontobj.DefaultObj;
import net.sf.openrocket.file.wavefrontobj.DefaultObjFace;
import net.sf.openrocket.file.wavefrontobj.ObjUtils;
@ -8,7 +9,7 @@ import java.util.List;
public class TubeExporter {
/**
* Add a tube mesh to the obj. The longitudinal axis is the y axis (in OBJ coordinate system).
* Add a tube mesh to the obj.
* @param obj The obj to add the mesh to
* @param groupName The name of the group to add the mesh to, or null if no group should be added (use the active group)
* @param bottomOuterRadius The outer radius of the bottom of the tube
@ -49,6 +50,7 @@ public class TubeExporter {
float x = bottomOuterRadius * (float) Math.cos(angle);
float z = bottomOuterRadius * (float) Math.sin(angle);
//FloatTuple vertex = ObjUtils.
obj.addVertex(x, 0, z);
obj.addNormal(x, 0, z); // This kind of normal ensures the object is smoothly rendered (like the 'Shade Smooth' option in Blender)

View File

@ -1,6 +1,8 @@
package net.sf.openrocket.file.wavefrontobj.export;
import net.sf.openrocket.document.OpenRocketDocumentFactory;
import net.sf.openrocket.file.wavefrontobj.CoordTransform;
import net.sf.openrocket.file.wavefrontobj.DefaultCoordTransform;
import net.sf.openrocket.rocketcomponent.AxialStage;
import net.sf.openrocket.rocketcomponent.BodyTube;
import net.sf.openrocket.rocketcomponent.LaunchLug;
@ -91,11 +93,12 @@ public class OBJExporterFactoryTest extends BaseTestCase {
Path tempFile = Files.createTempFile("testExport", ".obj");
finSet.setFinCount(1);
/*finSet.setFinCount(1);
finSet.setAngleOffset(Math.toRadians(45));
TestRockets.dumpRocket(rocket, "/Users/SiboVanGool/Downloads/test.ork");
OBJExporterFactory exporterFactory = new OBJExporterFactory(components, true, false, true,
TestRockets.dumpRocket(rocket, "/Users/SiboVanGool/Downloads/test.ork");*/
CoordTransform transformer = new DefaultCoordTransform(rocket.getLength());
OBJExporterFactory exporterFactory = new OBJExporterFactory(components, true, false, true, transformer,
"/Users/SiboVanGool/Downloads/testExport.obj");
exporterFactory.doExport();

View File

@ -49,6 +49,8 @@ import javax.swing.tree.DefaultTreeSelectionModel;
import javax.swing.tree.TreePath;
import javax.swing.tree.TreeSelectionModel;
import net.miginfocom.swing.MigLayout;
import net.sf.openrocket.file.wavefrontobj.CoordTransform;
import net.sf.openrocket.file.wavefrontobj.DefaultCoordTransform;
import net.sf.openrocket.file.wavefrontobj.export.OBJExporterFactory;
import net.sf.openrocket.gui.configdialog.SaveDesignInfoPanel;
import net.sf.openrocket.gui.dialogs.ErrorWarningDialog;
@ -1607,9 +1609,10 @@ public class BasicFrame extends JFrame {
//// BEGIN WAVEFRONT OBJ Save/Export Action
private void exportWavefrontOBJAction() {
// TODO: popup dialog for extra options (quality, whether to triangulate, whether to export materials, whether to save all subcomponents of the selected ones, whether to offset the object position to zero or to the location in the rocket, whether to scale the rocket etc.)
// TODO: popup dialog for extra options (quality, whether to triangulate, whether to export materials, whether to save all subcomponents of the selected ones, whether to offset the object position to zero or to the location in the rocket, whether to save the rocket dimensions in SI units or in mm (add tooltip text that mm is useful for 3D printing) etc.)
String filePath = "/Users/SiboVanGool/Downloads/test.obj";
OBJExporterFactory exporter = new OBJExporterFactory(getSelectedComponents(), false, false, true, filePath);
CoordTransform transformer = new DefaultCoordTransform(rocket.getLength());
OBJExporterFactory exporter = new OBJExporterFactory(getSelectedComponents(), false, false, true, transformer, filePath);
exporter.doExport();
}