From 6efd39b4539f7df0727ce2f1414ca528c139cff2 Mon Sep 17 00:00:00 2001 From: SiboVG Date: Wed, 9 Aug 2023 01:30:16 +0200 Subject: [PATCH] WIP - FIX COORDINATE SYSTEM!! --- .../file/wavefrontobj/ObjUtils.java | 25 ++++++++++ .../export/OBJExporterFactory.java | 14 ++++++ .../export/components/BodyTubeExporter.java | 15 ++---- .../export/components/FinSetExporter.java | 31 +++++------- .../export/components/LaunchLugExporter.java | 17 +++---- .../export/components/MassObjectExporter.java | 31 ++++++------ .../RadiusRingComponentExporter.java | 12 ++--- .../export/components/RailButtonExporter.java | 48 +++++++++---------- .../ThicknessRingComponentExporter.java | 13 ++--- .../export/components/TransitionExporter.java | 26 +++++----- .../export/components/TubeFinSetExporter.java | 20 ++++---- .../export/shapes/CylinderExporter.java | 11 ----- .../export/shapes/PolygonExporter.java | 25 ++-------- .../export/shapes/TubeExporter.java | 12 ----- .../export/OBJExporterFactoryTest.java | 21 ++++++-- 15 files changed, 153 insertions(+), 168 deletions(-) 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); } }