WIP - FIX COORDINATE SYSTEM!!

This commit is contained in:
SiboVG 2023-08-09 01:30:16 +02:00
parent 081afdb635
commit 6efd39b453
15 changed files with 153 additions and 168 deletions

View File

@ -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

View File

@ -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.
* <b>NOTE: </b> 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 <sibo.vangool@hotmail.com>
*/
public class OBJExporterFactory {
private final List<RocketComponent> components;

View File

@ -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);
}
}

View File

@ -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);
}
/**

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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,

View File

@ -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);
}
}

View File

@ -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<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 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);

View File

@ -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);
}
}

View File

@ -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);
}
}
}

View File

@ -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);
}
}
}

View File

@ -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);
}
}
}

View File

@ -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<RocketComponent> components = List.of(rocket);
List<RocketComponent> 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);
}
}