diff --git a/core/src/net/sf/openrocket/file/wavefrontobj/ObjUtils.java b/core/src/net/sf/openrocket/file/wavefrontobj/ObjUtils.java
index e1ceb8846..cbd30ec1a 100644
--- a/core/src/net/sf/openrocket/file/wavefrontobj/ObjUtils.java
+++ b/core/src/net/sf/openrocket/file/wavefrontobj/ObjUtils.java
@@ -4,6 +4,8 @@ import de.javagl.obj.FloatTuple;
import de.javagl.obj.Obj;
import de.javagl.obj.ObjFace;
import de.javagl.obj.ObjGroup;
+import net.sf.openrocket.rocketcomponent.RocketComponent;
+import net.sf.openrocket.util.Coordinate;
import java.util.List;
@@ -298,6 +300,29 @@ public class ObjUtils {
}
}
+
+ /**
+ * Translates the vertices in the obj file so that the component is at the specified location.
+ * See explanation in {@link net.sf.openrocket.file.wavefrontobj.export.OBJExporterFactory} about the difference in
+ * coordinate system between OpenRocket and Wavefront OBJ.
+ * @param obj The obj file to translate
+ * @param component The component to translate
+ * @param startIdx The index of the first vertex to translate
+ * @param endIdx The index of the last vertex to translate (inclusive)
+ * @param location The location to translate the component to (in OpenRocket coordinate system)
+ * @param yOffset The offset to apply to the y coordinate of the location
+ */
+ public static void translateVerticesFromComponentLocation(DefaultObj obj, RocketComponent component,
+ int startIdx, int endIdx, Coordinate location, double yOffset) {
+ final double rocketLength = component.getRocket().getLength();
+
+ // Translate the mesh
+ final float x = (float) location.y;
+ final float y = (float) (rocketLength + yOffset - location.x);
+ final float z = (float) - location.z;
+ ObjUtils.translateVertices(obj, startIdx, endIdx, x, y, z);
+ }
+
/**
* Merge a list of objs into a single obj.
* @param objs The objs to merge
diff --git a/core/src/net/sf/openrocket/file/wavefrontobj/export/OBJExporterFactory.java b/core/src/net/sf/openrocket/file/wavefrontobj/export/OBJExporterFactory.java
index 508144175..b99581da8 100644
--- a/core/src/net/sf/openrocket/file/wavefrontobj/export/OBJExporterFactory.java
+++ b/core/src/net/sf/openrocket/file/wavefrontobj/export/OBJExporterFactory.java
@@ -32,6 +32,20 @@ import java.util.HashSet;
import java.util.List;
import java.util.Set;
+/**
+ * Exporter for rocket components to a Wavefront OBJ file.
+ * NOTE: The coordinate system of the Wavefront OBJ file and OpenRocket is different.
+ * An OBJ file has the y-axis pointing up, the z-axis pointing towards the viewer, and the x-axis pointing to the right (right-handed system).
+ * OpenRocket uses a left-handed system with the y-axis pointing up, the z-axis pointing away from the viewer, and the
+ * x-axis pointing to the right (in the side view). Its origin is also at the tip of the rocket, whereas for the OBJ it
+ * would be the bottom of the rocket.
+ * => the following transformation applies from OBJ coordinate system to OpenRocket coordinate system:
+ * x = y
+ * y = rocketLength - x
+ * z = -z
+ *
+ * @author Sibo Van Gool
+ */
public class OBJExporterFactory {
private final List components;
diff --git a/core/src/net/sf/openrocket/file/wavefrontobj/export/components/BodyTubeExporter.java b/core/src/net/sf/openrocket/file/wavefrontobj/export/components/BodyTubeExporter.java
index 6b5611ae4..98df3f802 100644
--- a/core/src/net/sf/openrocket/file/wavefrontobj/export/components/BodyTubeExporter.java
+++ b/core/src/net/sf/openrocket/file/wavefrontobj/export/components/BodyTubeExporter.java
@@ -5,7 +5,6 @@ import net.sf.openrocket.file.wavefrontobj.ObjUtils;
import net.sf.openrocket.file.wavefrontobj.export.shapes.CylinderExporter;
import net.sf.openrocket.file.wavefrontobj.export.shapes.TubeExporter;
import net.sf.openrocket.rocketcomponent.BodyTube;
-import net.sf.openrocket.rocketcomponent.RocketComponent;
import net.sf.openrocket.util.Coordinate;
public class BodyTubeExporter extends RocketComponentExporter {
@@ -21,17 +20,16 @@ public class BodyTubeExporter extends RocketComponentExporter {
final float innerRadius = (float) bodyTube.getInnerRadius();
final float length = (float) bodyTube.getLength();
final boolean isFilled = bodyTube.isFilled();
- final double rocketLength = bodyTube.getRocket().getLength();
final Coordinate[] locations = bodyTube.getComponentLocations();
// Generate the mesh
for (Coordinate location : locations) {
- generateMesh(outerRadius, innerRadius, length, isFilled, rocketLength, location);
+ generateMesh(bodyTube, outerRadius, innerRadius, length, isFilled, location);
}
}
- private void generateMesh(float outerRadius, float innerRadius, float length, boolean isFilled,
- double rocketLength, Coordinate location) {
+ private void generateMesh(BodyTube bodyTube, float outerRadius, float innerRadius, float length, boolean isFilled,
+ Coordinate location) {
int startIdx = obj.getNumVertices();
if (isFilled || Float.compare(innerRadius, 0) == 0) {
@@ -46,10 +44,7 @@ public class BodyTubeExporter extends RocketComponentExporter {
int endIdx = Math.max(obj.getNumVertices() - 1, startIdx); // Clamp in case no vertices were added
- // Translate the mesh
- final float x = (float) location.y;
- final float y = (float) (rocketLength - length - location.x);
- final float z = (float) location.z;
- ObjUtils.translateVertices(obj, startIdx, endIdx, x, y, z);
+ // Translate the mesh to the position in the rocket
+ ObjUtils.translateVerticesFromComponentLocation(obj, bodyTube, startIdx, endIdx, location, -length);
}
}
diff --git a/core/src/net/sf/openrocket/file/wavefrontobj/export/components/FinSetExporter.java b/core/src/net/sf/openrocket/file/wavefrontobj/export/components/FinSetExporter.java
index 95a027e17..329482cbd 100644
--- a/core/src/net/sf/openrocket/file/wavefrontobj/export/components/FinSetExporter.java
+++ b/core/src/net/sf/openrocket/file/wavefrontobj/export/components/FinSetExporter.java
@@ -4,9 +4,6 @@ import net.sf.openrocket.file.wavefrontobj.DefaultObj;
import net.sf.openrocket.file.wavefrontobj.ObjUtils;
import net.sf.openrocket.file.wavefrontobj.export.shapes.PolygonExporter;
import net.sf.openrocket.rocketcomponent.FinSet;
-import net.sf.openrocket.rocketcomponent.RocketComponent;
-import net.sf.openrocket.rocketcomponent.position.AxialMethod;
-import net.sf.openrocket.util.ArrayUtils;
import net.sf.openrocket.util.Coordinate;
import java.util.ArrayList;
@@ -43,12 +40,12 @@ public class FinSetExporter extends RocketComponentExporter {
// Generate the fin meshes
for (int i = 0; i < locations.length; i++) {
- generateMesh(finSet,floatPoints, floatTabPoints, thickness, hasTabs, rocketLength, locations[i], angles[i]);
+ generateMesh(finSet,floatPoints, floatTabPoints, thickness, hasTabs, locations[i], angles[i]);
}
}
private void generateMesh(FinSet finSet, FloatPoints floatPoints, FloatPoints floatTabPoints, float thickness,
- boolean hasTabs, double rocketLength, Coordinate location, double angle) {
+ boolean hasTabs, Coordinate location, double angle) {
// Generate the mesh
final int startIdx = obj.getNumVertices();
final int normalsStartIdx = obj.getNumNormals();
@@ -58,7 +55,6 @@ public class FinSetExporter extends RocketComponentExporter {
floatPoints.getXCoords(), floatPoints.getYCoords(), thickness);
// Generate the fin tabs
- final int tabStartIdx = obj.getNumVertices();
if (hasTabs) {
PolygonExporter.addPolygonMesh(obj, null,
floatTabPoints.getXCoords(), floatTabPoints.getYCoords(), thickness);
@@ -67,23 +63,18 @@ public class FinSetExporter extends RocketComponentExporter {
int endIdx = Math.max(obj.getNumVertices() - 1, startIdx); // Clamp in case no vertices were added
int normalsEndIdx = Math.max(obj.getNumNormals() - 1, normalsStartIdx); // Clamp in case no normals were added
- // First rotate the fin for a correct orientation
- final float axialRot = (float) angle;
- final float cantAngle = (float) finSet.getCantAngle();
+ // First rotate for the cant angle
+ final float cantAngle = (float) - finSet.getCantAngle();
ObjUtils.rotateVertices(obj, startIdx, endIdx, normalsStartIdx, normalsEndIdx,
- -(float) Math.PI/2, cantAngle, axialRot, 0, 0, 0);
+ cantAngle, 0, 0, 0, (float) - finSet.getLength(), 0);
- // Translate the mesh
- final float x = (float) location.y;
- final float y = (float) (rocketLength - location.x);
- final float z = (float) location.z;
- ObjUtils.translateVertices(obj, startIdx, endIdx, x, y, z);
+ // Then do the axial rotation
+ final float axialRot = (float) angle;
+ ObjUtils.rotateVertices(obj, startIdx, endIdx, normalsStartIdx, normalsEndIdx,
+ 0, axialRot, 0, 0, 0, 0);
- // Offset the tabs
- if (hasTabs) {
- float yTab = - (float) finSet.getTabPosition(AxialMethod.TOP);
- ObjUtils.translateVertices(obj, tabStartIdx, endIdx, 0, yTab, 0);
- }
+ // Translate the mesh to the position in the rocket
+ ObjUtils.translateVerticesFromComponentLocation(obj, finSet, startIdx, endIdx, location, 0);
}
/**
diff --git a/core/src/net/sf/openrocket/file/wavefrontobj/export/components/LaunchLugExporter.java b/core/src/net/sf/openrocket/file/wavefrontobj/export/components/LaunchLugExporter.java
index 223f7fee9..4d066cd25 100644
--- a/core/src/net/sf/openrocket/file/wavefrontobj/export/components/LaunchLugExporter.java
+++ b/core/src/net/sf/openrocket/file/wavefrontobj/export/components/LaunchLugExporter.java
@@ -5,7 +5,6 @@ import net.sf.openrocket.file.wavefrontobj.ObjUtils;
import net.sf.openrocket.file.wavefrontobj.export.shapes.CylinderExporter;
import net.sf.openrocket.file.wavefrontobj.export.shapes.TubeExporter;
import net.sf.openrocket.rocketcomponent.LaunchLug;
-import net.sf.openrocket.rocketcomponent.RocketComponent;
import net.sf.openrocket.util.Coordinate;
public class LaunchLugExporter extends RocketComponentExporter {
@@ -18,19 +17,18 @@ public class LaunchLugExporter extends RocketComponentExporter {
final LaunchLug lug = (LaunchLug) component;
final Coordinate[] locations = lug.getComponentLocations();
- final double rocketLength = lug.getRocket().getLength();
final float outerRadius = (float) lug.getOuterRadius();
final float innerRadius = (float) lug.getInnerRadius();
final float length = (float) lug.getLength();
// Generate the mesh
for (Coordinate location : locations) {
- generateMesh(lug, outerRadius, innerRadius, length, rocketLength, location);
+ generateMesh(lug, outerRadius, innerRadius, length, location);
}
}
- private void generateMesh(LaunchLug lug, float outerRadius, float innerRadius, float length, double rocketLength, Coordinate location) {
- int startIdx2 = obj.getNumVertices();
+ private void generateMesh(LaunchLug lug, float outerRadius, float innerRadius, float length, Coordinate location) {
+ int startIdx = obj.getNumVertices();
// Generate the instance mesh
if (Float.compare(innerRadius, 0) == 0) {
@@ -43,12 +41,9 @@ public class LaunchLugExporter extends RocketComponentExporter {
}
}
- int endIdx2 = Math.max(obj.getNumVertices() - 1, startIdx2); // Clamp in case no vertices were added
+ int endIdx = Math.max(obj.getNumVertices() - 1, startIdx); // Clamp in case no vertices were added
- // Translate the lug instance
- final float x = (float) location.y;
- final float y = (float) (rocketLength - lug.getLength() - location.x);
- final float z = (float) location.z;
- ObjUtils.translateVertices(obj, startIdx2, endIdx2, x, y, z);
+ // Translate the mesh to the position in the rocket
+ ObjUtils.translateVerticesFromComponentLocation(obj, lug, startIdx, endIdx, location, -length);
}
}
diff --git a/core/src/net/sf/openrocket/file/wavefrontobj/export/components/MassObjectExporter.java b/core/src/net/sf/openrocket/file/wavefrontobj/export/components/MassObjectExporter.java
index 3f6da7f9f..0902f4c0b 100644
--- a/core/src/net/sf/openrocket/file/wavefrontobj/export/components/MassObjectExporter.java
+++ b/core/src/net/sf/openrocket/file/wavefrontobj/export/components/MassObjectExporter.java
@@ -4,7 +4,6 @@ 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.rocketcomponent.MassObject;
-import net.sf.openrocket.rocketcomponent.RocketComponent;
import net.sf.openrocket.util.Coordinate;
import net.sf.openrocket.util.RocketComponentUtils;
@@ -20,20 +19,18 @@ public class MassObjectExporter extends RocketComponentExporter {
obj.setActiveGroupNames(groupName);
final Coordinate[] locations = massObject.getComponentLocations();
- final double rocketLength = massObject.getRocket().getLength();
final int numSides = LOD.getValue() / 2;
final int numStacks = LOD.getValue() / 2;
// Generate the mesh
for (Coordinate location : locations) {
- generateMesh(massObject, numSides, numStacks, rocketLength, location);
+ generateMesh(massObject, numSides, numStacks, location);
}
-
}
- private void generateMesh(MassObject massObject, int numSides, int numStacks, double rocketLength, Coordinate location) {
+ private void generateMesh(MassObject massObject, 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 verticesStartIdx = obj.getNumVertices();
+ int startIdx = obj.getNumVertices();
int normalsStartIdx = obj.getNumNormals();
double dy = massObject.getLength() / numStacks;
double da = 2.0f * Math.PI / numSides;
@@ -72,7 +69,7 @@ public class MassObjectExporter extends RocketComponentExporter {
}
}
- int endIdx = Math.max(obj.getNumVertices() - 1, verticesStartIdx); // Clamp in case no vertices were added
+ int endIdx = Math.max(obj.getNumVertices() - 1, startIdx); // Clamp in case no vertices were added
// Create bottom tip faces
for (int i = 0; i < numSides; i++) {
@@ -86,7 +83,7 @@ public class MassObjectExporter extends RocketComponentExporter {
int[] normalIndices = vertexIndices.clone(); // For a smooth surface, the vertex and normal indices are the same
ObjUtils.offsetIndex(normalIndices, normalsStartIdx);
- ObjUtils.offsetIndex(vertexIndices, verticesStartIdx); // Only do this after normals are added, since the vertex indices are used for normals
+ ObjUtils.offsetIndex(vertexIndices, startIdx); // Only do this after normals are added, since the vertex indices are used for normals
DefaultObjFace face = new DefaultObjFace(vertexIndices, null, normalIndices);
obj.addFace(face);
@@ -106,7 +103,7 @@ public class MassObjectExporter extends RocketComponentExporter {
int[] normalIndices = vertexIndices.clone(); // For a smooth surface, the vertex and normal indices are the same
ObjUtils.offsetIndex(normalIndices, normalsStartIdx);
- ObjUtils.offsetIndex(vertexIndices, verticesStartIdx); // Only do this after normals are added, since the vertex indices are used for normals
+ ObjUtils.offsetIndex(vertexIndices, startIdx); // Only do this after normals are added, since the vertex indices are used for normals
DefaultObjFace face = new DefaultObjFace(vertexIndices, null, normalIndices);
obj.addFace(face);
@@ -134,12 +131,18 @@ public class MassObjectExporter extends RocketComponentExporter {
obj.addFace(face);
}
- // Translate the mesh
+ // 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());
+ }
+
+ private static Coordinate getOffsetLocation(MassObject massObject, Coordinate location) {
+ // ! This is all still referenced to the OpenRocket coordinate system, not the OBJ one
final double radialPosition = massObject.getRadialPosition();
final double radialDirection = massObject.getRadialDirection();
- final float x = (float) (location.y + radialPosition * Math.cos(radialDirection));
- final float y = (float) (rocketLength - massObject.getLength() - location.x);
- final float z = (float) (location.z + radialPosition * Math.sin(radialDirection));
- ObjUtils.translateVertices(obj, verticesStartIdx, endIdx, x, y, z);
+ final double y = location.y + radialPosition * Math.cos(radialDirection);
+ final double z = location.z + radialPosition * Math.sin(radialDirection);
+ return new Coordinate(location.x, y, z);
}
}
diff --git a/core/src/net/sf/openrocket/file/wavefrontobj/export/components/RadiusRingComponentExporter.java b/core/src/net/sf/openrocket/file/wavefrontobj/export/components/RadiusRingComponentExporter.java
index 6273301b5..b4ccd3ef8 100644
--- a/core/src/net/sf/openrocket/file/wavefrontobj/export/components/RadiusRingComponentExporter.java
+++ b/core/src/net/sf/openrocket/file/wavefrontobj/export/components/RadiusRingComponentExporter.java
@@ -5,7 +5,6 @@ import net.sf.openrocket.file.wavefrontobj.ObjUtils;
import net.sf.openrocket.file.wavefrontobj.export.shapes.CylinderExporter;
import net.sf.openrocket.file.wavefrontobj.export.shapes.TubeExporter;
import net.sf.openrocket.rocketcomponent.RadiusRingComponent;
-import net.sf.openrocket.rocketcomponent.RocketComponent;
import net.sf.openrocket.util.Coordinate;
public class RadiusRingComponentExporter extends RocketComponentExporter {
@@ -25,11 +24,11 @@ public class RadiusRingComponentExporter extends RocketComponentExporter {
// Generate the mesh
for (Coordinate location : locations) {
- generateMesh(outerRadius, innerRadius, thickness, rocketLength, location);
+ generateMesh(outerRadius, innerRadius, thickness, location);
}
}
- private void generateMesh(float outerRadius, float innerRadius, float thickness, double rocketLength, Coordinate location) {
+ private void generateMesh(float outerRadius, float innerRadius, float thickness, Coordinate location) {
int startIdx = obj.getNumVertices();
if (Float.compare(innerRadius, 0) == 0) {
@@ -44,10 +43,7 @@ public class RadiusRingComponentExporter extends RocketComponentExporter {
int endIdx = Math.max(obj.getNumVertices() - 1, startIdx); // Clamp in case no vertices were added
- // Translate the mesh
- final float x = (float) location.y;
- final float y = (float) (rocketLength - thickness - location.x);
- final float z = (float) location.z;
- ObjUtils.translateVertices(obj, startIdx, endIdx, x, y, z);
+ // Translate the mesh to the position in the rocket
+ ObjUtils.translateVerticesFromComponentLocation(obj, component, startIdx, endIdx, location, -thickness);
}
}
diff --git a/core/src/net/sf/openrocket/file/wavefrontobj/export/components/RailButtonExporter.java b/core/src/net/sf/openrocket/file/wavefrontobj/export/components/RailButtonExporter.java
index b081432a9..3ca359563 100644
--- a/core/src/net/sf/openrocket/file/wavefrontobj/export/components/RailButtonExporter.java
+++ b/core/src/net/sf/openrocket/file/wavefrontobj/export/components/RailButtonExporter.java
@@ -38,19 +38,18 @@ public class RailButtonExporter extends RocketComponentExporter {
final float flangeHeight = (float) railButton.getFlangeHeight();
final float screwHeight = (float) railButton.getScrewHeight();
final Coordinate[] locations = railButton.getComponentLocations();
- final double[] angles = railButton.getInstanceAngles();
- final double rocketLength = railButton.getRocket().getLength();
+ final double[] angles = railButton.getComponentAngles();
// Generate the mesh
for (int i = 0; i < locations.length; i++) {
generateMesh(outerRadius, innerRadius, baseHeight, innerHeight, flangeHeight, screwHeight,
- rocketLength, locations[i], angles[i]);
+ locations[i], angles[i]);
}
}
private void generateMesh(float outerRadius, float innerRadius, float baseHeight, float innerHeight, float flangeHeight,
- float screwHeight, double rocketLength, Coordinate location, double angle) {
- final int vertexStartIdx = obj.getNumVertices();
+ float screwHeight, Coordinate location, double angle) {
+ final int startIdx = obj.getNumVertices();
final int normalStartIdx = obj.getNumNormals();
// Generate base cylinder
@@ -94,21 +93,20 @@ public class RailButtonExporter extends RocketComponentExporter {
}
- final int vertexEndIdx = Math.max(obj.getNumVertices() - 1, vertexStartIdx);
+ final int endIdx = Math.max(obj.getNumVertices() - 1, startIdx);
final int normalEndIdx = Math.max(obj.getNumNormals() - 1, normalStartIdx);
// Rotate the mesh (also PI/2!)
- final float rX = - (float) Math.PI / 2;
+ final float rX = 0;
final float rY = (float) angle;
- final float rZ = 0;
- ObjUtils.rotateVertices(obj, vertexStartIdx, vertexEndIdx, normalStartIdx, normalEndIdx,
+ final float rZ = (float) - Math.PI / 2;
+ ObjUtils.rotateVertices(obj, startIdx, endIdx, normalStartIdx, normalEndIdx,
rX, rY, rZ, 0, 0, 0);
- // Translate the mesh
- final float x = (float) location.y;
- final float y = (float) location.x;
- final float z = (float) location.z;
- ObjUtils.translateVertices(obj, vertexStartIdx, vertexEndIdx, x, y, z);
+ ObjUtils.translateVertices(obj, startIdx, endIdx, 1, 0, 0);
+
+ // Translate the mesh to the position in the rocket
+ ObjUtils.translateVerticesFromComponentLocation(obj, component, startIdx, endIdx, location, 0);
}
private void addScrew(DefaultObj obj, float baseHeight, float innerHeight, float flangeHeight, float outerRadius,
@@ -149,14 +147,14 @@ public class RailButtonExporter extends RocketComponentExporter {
// Generate the faces between the flange cylinder and the quad faces
for (int i = 0; i < nrOfSlices; i++) {
- int nextIdx = (i+1) % nrOfSlices;
- int[] vertexIndices = new int[]{
+ int nextIdx = (i + 1) % nrOfSlices;
+ int[] vertexIndices = new int[] {
flangeCylinderTopVertices.get(i), // Bottom-left of quad
startIdx + i, // Top-left of quad
startIdx + nextIdx, // Top-right of quad
flangeCylinderTopVertices.get(nextIdx), // Bottom-right of quad
};
- int[] normalIndices = new int[]{
+ int[] normalIndices = new int[] {
flangeCylinderTopVertices.get(i), // Bottom-left of quad
normalStartIdx + i, // Top-left of quad
normalStartIdx + nextIdx, // Top-right of quad
@@ -170,13 +168,13 @@ public class RailButtonExporter extends RocketComponentExporter {
}
// Generate the quad mesh faces (no tip)
- for (int i = 0; i <= nrOfStacks-2; i++) {
+ for (int i = 0; i <= nrOfStacks-3; i++) { // We do -3 instead of -2 because we offset the i entirely by starting at 0 instead of 1 (so we don't have to offset the indices)
for (int j = 0; j < nrOfSlices; j++) {
- int nextIdx = (j+1) % nrOfSlices;
- int[] vertexIndices = new int[]{
+ int nextIdx = (j + 1) % nrOfSlices;
+ int[] vertexIndices = new int[] {
i * nrOfSlices + j, // Bottom-left of quad outside vertex
- (i+1) * nrOfSlices + j, // Top-left of quad outside vertex
- (i+1) * nrOfSlices + nextIdx, // Top-right of quad inside vertex
+ (i + 1) * nrOfSlices + j, // Top-left of quad outside vertex
+ (i + 1) * nrOfSlices + nextIdx, // Top-right of quad inside vertex
i * nrOfSlices + nextIdx // Bottom-right of quad inside vertex
};
int[] normalIndices = vertexIndices.clone();
@@ -193,13 +191,13 @@ public class RailButtonExporter extends RocketComponentExporter {
final int endIdx = Math.max(obj.getNumVertices() - 1, startIdx);
final int normalEndIdx = Math.max(obj.getNumNormals() - 1, normalStartIdx);
for (int i = 0; i < nrOfSlices; i++) {
- int nextIdx = (i+1) % nrOfSlices;
- int[] vertexIndices = new int[]{
+ int nextIdx = (i + 1) % nrOfSlices;
+ int[] vertexIndices = new int[] {
endIdx, // Tip vertex
endIdx - nrOfSlices + nextIdx,
endIdx - nrOfSlices + i,
};
- int[] normalIndices = new int[]{
+ int[] normalIndices = new int[] {
normalEndIdx, // Tip normal
normalEndIdx - nrOfSlices + nextIdx,
normalEndIdx - nrOfSlices + i,
diff --git a/core/src/net/sf/openrocket/file/wavefrontobj/export/components/ThicknessRingComponentExporter.java b/core/src/net/sf/openrocket/file/wavefrontobj/export/components/ThicknessRingComponentExporter.java
index dd054fbb1..0c10ad97d 100644
--- a/core/src/net/sf/openrocket/file/wavefrontobj/export/components/ThicknessRingComponentExporter.java
+++ b/core/src/net/sf/openrocket/file/wavefrontobj/export/components/ThicknessRingComponentExporter.java
@@ -4,7 +4,6 @@ import net.sf.openrocket.file.wavefrontobj.DefaultObj;
import net.sf.openrocket.file.wavefrontobj.ObjUtils;
import net.sf.openrocket.file.wavefrontobj.export.shapes.CylinderExporter;
import net.sf.openrocket.file.wavefrontobj.export.shapes.TubeExporter;
-import net.sf.openrocket.rocketcomponent.RocketComponent;
import net.sf.openrocket.rocketcomponent.ThicknessRingComponent;
import net.sf.openrocket.util.Coordinate;
@@ -20,16 +19,15 @@ public class ThicknessRingComponentExporter extends RocketComponentExporter {
final float outerRadius = (float) thicknessRing.getOuterRadius();
final float innerRadius = (float) thicknessRing.getInnerRadius();
final float length = (float) thicknessRing.getLength();
- final double rocketLength = thicknessRing.getRocket().getLength();
final Coordinate[] locations = thicknessRing.getComponentLocations();
// Generate the mesh
for (Coordinate location : locations) {
- generateMesh(outerRadius, innerRadius, length, rocketLength, location);
+ generateMesh(outerRadius, innerRadius, length, location);
}
}
- private void generateMesh(float outerRadius, float innerRadius, float length, double rocketLength, Coordinate location) {
+ private void generateMesh(float outerRadius, float innerRadius, float length, Coordinate location) {
int startIdx = obj.getNumVertices();
if (Float.compare(innerRadius, 0) == 0) {
@@ -44,10 +42,7 @@ public class ThicknessRingComponentExporter extends RocketComponentExporter {
int endIdx = Math.max(obj.getNumVertices() - 1, startIdx); // Clamp in case no vertices were added
- // Translate the mesh
- final float x = (float) location.y;
- final float y = (float) (rocketLength - length - location.x);
- final float z = (float) location.z;
- ObjUtils.translateVertices(obj, startIdx, endIdx, x, y, z);
+ // Translate the mesh to the position in the rocket
+ ObjUtils.translateVerticesFromComponentLocation(obj, component, startIdx, endIdx, location, -length);
}
}
diff --git a/core/src/net/sf/openrocket/file/wavefrontobj/export/components/TransitionExporter.java b/core/src/net/sf/openrocket/file/wavefrontobj/export/components/TransitionExporter.java
index c65b7af2c..24bc21829 100644
--- a/core/src/net/sf/openrocket/file/wavefrontobj/export/components/TransitionExporter.java
+++ b/core/src/net/sf/openrocket/file/wavefrontobj/export/components/TransitionExporter.java
@@ -35,15 +35,14 @@ public class TransitionExporter extends RocketComponentExporter {
obj.setActiveGroupNames(groupName);
final Coordinate[] locations = transition.getComponentLocations();
- final double rocketLength = transition.getRocket().getLength();
// Generate the mesh
for (Coordinate location : locations) {
- generateMesh(transition, rocketLength, location);
+ generateMesh(transition, location);
}
}
- private void generateMesh(Transition transition, double rocketLength, Coordinate location) {
+ private void generateMesh(Transition transition, Coordinate location) {
int startIdx = obj.getNumVertices();
final boolean hasForeShoulder = Double.compare(transition.getForeShoulderRadius(), 0) > 0
@@ -104,11 +103,8 @@ public class TransitionExporter extends RocketComponentExporter {
int endIdx = Math.max(obj.getNumVertices() - 1, startIdx); // Clamp in case no vertices were added
- // Translate the mesh
- final float x = (float) location.y;
- final float y = (float) (rocketLength - transition.getLength() - location.x);
- final float z = (float) location.z;
- ObjUtils.translateVertices(obj, startIdx, endIdx, x, y, z);
+ // Translate the mesh to the position in the rocket
+ ObjUtils.translateVerticesFromComponentLocation(obj, transition, startIdx, endIdx, location, -transition.getLength());
}
/**
@@ -127,7 +123,7 @@ public class TransitionExporter extends RocketComponentExporter {
List foreRingVertices, List 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 verticesStartIdx = obj.getNumVertices();
+ final int startIdx = obj.getNumVertices();
final int normalsStartIdx = obj.getNumNormals();
final double dyBase = transition.getLength() / numStacks; // Base step size in the longitudinal direction
@@ -255,16 +251,16 @@ public class TransitionExporter extends RocketComponentExporter {
// Create aft/fore tip faces
if (isAftTip || isForeTip) {
- addTipFaces(obj, numSlices, isOutside, isAftTip, normalsStartIdx, verticesStartIdx);
+ addTipFaces(obj, numSlices, isOutside, isAftTip, startIdx, normalsStartIdx);
}
// Create regular faces
- int corrVStartIdx = isAftTip ? verticesStartIdx + 1 : verticesStartIdx;
+ int corrVStartIdx = isAftTip ? startIdx + 1 : startIdx;
int corrNStartIdx = isAftTip ? normalsStartIdx + 1 : normalsStartIdx;
addQuadFaces(obj, numSlices, actualNumStacks, corrVStartIdx, corrNStartIdx, isOutside);
}
- private static void addTipFaces(DefaultObj obj, int numSlices, boolean isOutside, boolean isAftTip, int normalsStartIdx, int verticesStartIdx) {
+ private static void addTipFaces(DefaultObj obj, 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;
@@ -282,7 +278,7 @@ public class TransitionExporter extends RocketComponentExporter {
normalIndices = vertexIndices.clone(); // No need to reverse, already done by vertices
ObjUtils.offsetIndex(normalIndices, normalsStartIdx);
- ObjUtils.offsetIndex(vertexIndices, verticesStartIdx); // Do this last, otherwise the normal indices will be wrong
+ ObjUtils.offsetIndex(vertexIndices, startIdx); // Do this last, otherwise the normal indices will be wrong
}
// Fore tip
else {
@@ -334,7 +330,7 @@ public class TransitionExporter extends RocketComponentExporter {
}
}
- private static void addQuadFaces(DefaultObj obj, int numSlices, int numStacks, int verticesStartIdx, int normalsStartIdx, boolean isOutside) {
+ private static void addQuadFaces(DefaultObj obj, 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;
@@ -349,7 +345,7 @@ public class TransitionExporter extends RocketComponentExporter {
int[] normalIndices = vertexIndices.clone(); // No reversing needed, already done by vertices
ObjUtils.offsetIndex(normalIndices, normalsStartIdx);
- ObjUtils.offsetIndex(vertexIndices, verticesStartIdx); // Do this last, otherwise the normal indices will be wrong
+ ObjUtils.offsetIndex(vertexIndices, startIdx); // Do this last, otherwise the normal indices will be wrong
DefaultObjFace face = new DefaultObjFace(vertexIndices, null, normalIndices);
obj.addFace(face);
diff --git a/core/src/net/sf/openrocket/file/wavefrontobj/export/components/TubeFinSetExporter.java b/core/src/net/sf/openrocket/file/wavefrontobj/export/components/TubeFinSetExporter.java
index fdd9e8c2d..0393bf58b 100644
--- a/core/src/net/sf/openrocket/file/wavefrontobj/export/components/TubeFinSetExporter.java
+++ b/core/src/net/sf/openrocket/file/wavefrontobj/export/components/TubeFinSetExporter.java
@@ -2,9 +2,7 @@ package net.sf.openrocket.file.wavefrontobj.export.components;
import net.sf.openrocket.file.wavefrontobj.DefaultObj;
import net.sf.openrocket.file.wavefrontobj.ObjUtils;
-import net.sf.openrocket.file.wavefrontobj.export.shapes.PolygonExporter;
import net.sf.openrocket.file.wavefrontobj.export.shapes.TubeExporter;
-import net.sf.openrocket.rocketcomponent.RocketComponent;
import net.sf.openrocket.rocketcomponent.TubeFinSet;
import net.sf.openrocket.util.Coordinate;
@@ -45,12 +43,18 @@ public class TubeFinSetExporter extends RocketComponentExporter {
int endIdx = Math.max(obj.getNumVertices() - 1, startIdx); // Clamp in case no vertices were added
- // Translate the mesh
- final float dx = outerRadius * (float) Math.cos(angle);
+ // 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 launch lug
+ Coordinate offsetLocation = getOffsetLocation(outerRadius, location, angle);
+ ObjUtils.translateVerticesFromComponentLocation(obj, component, startIdx, endIdx, offsetLocation, -length);
+ }
+
+ private static Coordinate getOffsetLocation(float outerRadius, Coordinate location, double angle) {
+ // ! This is all still referenced to the OpenRocket coordinate system, not the OBJ one
+ final float dy = outerRadius * (float) Math.cos(angle);
final float dz = outerRadius * (float) Math.sin(angle);
- final float x = (float) location.y + dx;
- final float y = (float) (rocketLength - length - location.x);
- final float z = (float) location.z + dz;
- ObjUtils.translateVertices(obj, startIdx, endIdx, x, y, z);
+ final double y = location.y + dy;
+ final double z = location.z + dz;
+ return new Coordinate(location.x, y, z);
}
}
diff --git a/core/src/net/sf/openrocket/file/wavefrontobj/export/shapes/CylinderExporter.java b/core/src/net/sf/openrocket/file/wavefrontobj/export/shapes/CylinderExporter.java
index 3cf100876..c4adec85b 100644
--- a/core/src/net/sf/openrocket/file/wavefrontobj/export/shapes/CylinderExporter.java
+++ b/core/src/net/sf/openrocket/file/wavefrontobj/export/shapes/CylinderExporter.java
@@ -1,12 +1,9 @@
package net.sf.openrocket.file.wavefrontobj.export.shapes;
-import de.javagl.obj.ObjWriter;
import net.sf.openrocket.file.wavefrontobj.DefaultObj;
import net.sf.openrocket.file.wavefrontobj.DefaultObjFace;
import net.sf.openrocket.file.wavefrontobj.ObjUtils;
-import java.io.FileOutputStream;
-import java.io.OutputStream;
import java.util.List;
public class CylinderExporter {
@@ -171,12 +168,4 @@ public class CylinderExporter {
public static void addCylinderMesh(DefaultObj obj, String groupName, float radius, float height, boolean solid) {
addCylinderMesh(obj, groupName, radius, height, solid, ObjUtils.LevelOfDetail.NORMAL);
}
-
- public static void main(String[] args) throws Exception {
- DefaultObj obj = new DefaultObj();
- addCylinderMesh(obj, "cylinder", 1, 2, 15, true, null, null);
- try (OutputStream objOutputStream = new FileOutputStream("/Users/SiboVanGool/Downloads/cylinder.obj")) {
- ObjWriter.write(obj, objOutputStream);
- }
- }
}
diff --git a/core/src/net/sf/openrocket/file/wavefrontobj/export/shapes/PolygonExporter.java b/core/src/net/sf/openrocket/file/wavefrontobj/export/shapes/PolygonExporter.java
index 4ba17ad00..bcdf31918 100644
--- a/core/src/net/sf/openrocket/file/wavefrontobj/export/shapes/PolygonExporter.java
+++ b/core/src/net/sf/openrocket/file/wavefrontobj/export/shapes/PolygonExporter.java
@@ -1,18 +1,13 @@
package net.sf.openrocket.file.wavefrontobj.export.shapes;
-import de.javagl.obj.ObjWriter;
import net.sf.openrocket.file.wavefrontobj.DefaultObj;
import net.sf.openrocket.file.wavefrontobj.DefaultObjFace;
-import net.sf.openrocket.file.wavefrontobj.DefaultObjGroup;
import net.sf.openrocket.file.wavefrontobj.ObjUtils;
-import java.io.FileOutputStream;
-import java.io.OutputStream;
-
public class PolygonExporter {
/**
- * Add a polygon mesh to the obj. It is drawn in the XY plane with the bottom left corner at the origin.
+ * Add a polygon mesh to the obj. It is drawn in the X-Y plane (negative Y) with the bottom left corner at the origin.
* @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 pointLocationsX The x locations of the points --> NOTE: points should follow a clockwise direction
@@ -37,12 +32,12 @@ public class PolygonExporter {
// Generate front face vertices
for (int i = 0; i < pointLocationsX.length; i++) {
- obj.addVertex(pointLocationsX[i], pointLocationsY[i], thickness/2);
+ obj.addVertex(pointLocationsY[i], -pointLocationsX[i], thickness/2);
}
// Generate back face vertices
for (int i = 0; i < pointLocationsX.length; i++) {
- obj.addVertex(pointLocationsX[i], pointLocationsY[i], -thickness/2);
+ obj.addVertex(pointLocationsY[i], -pointLocationsX[i], -thickness/2);
}
// Create front face
@@ -79,8 +74,8 @@ public class PolygonExporter {
ObjUtils.offsetIndex(vertexIndices, verticesStartIdx);
// Calculate normals for side faces
- final float dx = pointLocationsX[nextIdx] - pointLocationsX[i];
- final float dy = pointLocationsY[nextIdx] - pointLocationsY[i];
+ final float dx = pointLocationsY[nextIdx] - pointLocationsY[i];
+ final float dy = pointLocationsX[nextIdx] - pointLocationsX[i];
// Perpendicular vector in 2D (for clockwise vertices)
final float nx = -dy;
@@ -111,14 +106,4 @@ public class PolygonExporter {
throw new IllegalArgumentException("The first and last points must be different");
}
}
-
- public static void main(String[] args) throws Exception {
- DefaultObj obj = new DefaultObj();
- float[] x = new float[]{0, 0.3f, 1, 0.7f};
- float[] y = new float[]{0, 0.5f, 0.5f, 0};
- addPolygonMesh(obj, "polygon", x, y, 0.025f);
- try (OutputStream objOutputStream = new FileOutputStream("/Users/SiboVanGool/Downloads/poly.obj")) {
- ObjWriter.write(obj, objOutputStream);
- }
- }
}
diff --git a/core/src/net/sf/openrocket/file/wavefrontobj/export/shapes/TubeExporter.java b/core/src/net/sf/openrocket/file/wavefrontobj/export/shapes/TubeExporter.java
index b9cc5eed0..2d15b8ca9 100644
--- a/core/src/net/sf/openrocket/file/wavefrontobj/export/shapes/TubeExporter.java
+++ b/core/src/net/sf/openrocket/file/wavefrontobj/export/shapes/TubeExporter.java
@@ -1,12 +1,9 @@
package net.sf.openrocket.file.wavefrontobj.export.shapes;
-import de.javagl.obj.ObjWriter;
import net.sf.openrocket.file.wavefrontobj.DefaultObj;
import net.sf.openrocket.file.wavefrontobj.DefaultObjFace;
import net.sf.openrocket.file.wavefrontobj.ObjUtils;
-import java.io.FileOutputStream;
-import java.io.OutputStream;
import java.util.List;
public class TubeExporter {
@@ -237,13 +234,4 @@ public class TubeExporter {
public static void addTubeMesh(DefaultObj obj, String groupName, float outerRadius, float innerRadius, float height) {
addTubeMesh(obj, groupName, outerRadius, outerRadius, innerRadius, innerRadius, height);
}
-
- public static void main(String[] args) throws Exception {
- DefaultObj obj = new DefaultObj();
- //addTubeMesh(obj, "tube", 0.1f, 0.085f, 0.3f);
- addTubeMesh(obj, "tube", 0.14f, 0.06f, 0.13f, 0.05f, 0.3f);
- try (OutputStream objOutputStream = new FileOutputStream("/Users/SiboVanGool/Downloads/tube.obj")) {
- ObjWriter.write(obj, objOutputStream);
- }
- }
}
diff --git a/core/test/net/sf/openrocket/file/wavefrontobj/export/OBJExporterFactoryTest.java b/core/test/net/sf/openrocket/file/wavefrontobj/export/OBJExporterFactoryTest.java
index 067e459d9..fe9a724b6 100644
--- a/core/test/net/sf/openrocket/file/wavefrontobj/export/OBJExporterFactoryTest.java
+++ b/core/test/net/sf/openrocket/file/wavefrontobj/export/OBJExporterFactoryTest.java
@@ -3,7 +3,6 @@ package net.sf.openrocket.file.wavefrontobj.export;
import net.sf.openrocket.document.OpenRocketDocumentFactory;
import net.sf.openrocket.rocketcomponent.AxialStage;
import net.sf.openrocket.rocketcomponent.BodyTube;
-import net.sf.openrocket.rocketcomponent.FinSet;
import net.sf.openrocket.rocketcomponent.LaunchLug;
import net.sf.openrocket.rocketcomponent.NoseCone;
import net.sf.openrocket.rocketcomponent.Parachute;
@@ -17,11 +16,14 @@ import net.sf.openrocket.util.BaseTestCase.BaseTestCase;
import net.sf.openrocket.util.TestRockets;
import org.junit.Test;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
import java.util.List;
public class OBJExporterFactoryTest extends BaseTestCase {
@Test
- public void testExport() {
+ public void testExport() throws IOException {
Rocket rocket = OpenRocketDocumentFactory.createNewRocket().getRocket();
AxialStage sustainer = rocket.getStage(0);
@@ -52,7 +54,8 @@ public class OBJExporterFactoryTest extends BaseTestCase {
finSet.setRootChord(0.05);
finSet.setTabLength(0.03);
finSet.setTabHeight(0.01);
- finSet.setTabOffset(0);
+ finSet.setTabOffset(-0.0075);
+ finSet.setCantAngle(Math.toRadians(10));
bodyTube.addChild(finSet);
TubeFinSet tubeFinSet = new TubeFinSet();
@@ -82,12 +85,20 @@ public class OBJExporterFactoryTest extends BaseTestCase {
RailButton railButton = new RailButton();
railButton.setScrewHeight(0.0025);
+ railButton.setAngleOffset(Math.toRadians(67));
bodyTube.addChild(railButton);
+ List components = List.of(rocket);
- List components = List.of(noseCone);
+ Path tempFile = Files.createTempFile("testExport", ".obj");
- OBJExporterFactory exporterFactory = new OBJExporterFactory(components, false, false, true,
+ finSet.setFinCount(1);
+ finSet.setAngleOffset(Math.toRadians(45));
+
+ TestRockets.dumpRocket(rocket, "/Users/SiboVanGool/Downloads/test.ork");
+ OBJExporterFactory exporterFactory = new OBJExporterFactory(components, true, false, true,
"/Users/SiboVanGool/Downloads/testExport.obj");
exporterFactory.doExport();
+
+ Files.delete(tempFile);
}
}