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 19dc426b3..dd7a9f134 100644 --- a/core/src/net/sf/openrocket/file/wavefrontobj/export/OBJExporterFactory.java +++ b/core/src/net/sf/openrocket/file/wavefrontobj/export/OBJExporterFactory.java @@ -8,6 +8,7 @@ import net.sf.openrocket.file.wavefrontobj.export.components.BodyTubeExporter; import net.sf.openrocket.file.wavefrontobj.export.components.FinSetExporter; import net.sf.openrocket.file.wavefrontobj.export.components.LaunchLugExporter; import net.sf.openrocket.file.wavefrontobj.export.components.MassObjectExporter; +import net.sf.openrocket.file.wavefrontobj.export.components.MotorExporter; import net.sf.openrocket.file.wavefrontobj.export.components.RailButtonExporter; import net.sf.openrocket.file.wavefrontobj.export.components.RocketComponentExporter; import net.sf.openrocket.file.wavefrontobj.export.components.RingComponentExporter; @@ -21,6 +22,7 @@ import net.sf.openrocket.rocketcomponent.InstanceContext; import net.sf.openrocket.rocketcomponent.InstanceMap; import net.sf.openrocket.rocketcomponent.LaunchLug; import net.sf.openrocket.rocketcomponent.MassObject; +import net.sf.openrocket.rocketcomponent.MotorMount; import net.sf.openrocket.rocketcomponent.RailButton; import net.sf.openrocket.rocketcomponent.RingComponent; import net.sf.openrocket.rocketcomponent.RocketComponent; @@ -135,16 +137,15 @@ public class OBJExporterFactory { continue; } + // Get the instance transforms InstanceMap map = configuration.getActiveInstances(); ArrayList contexts = map.get(component); contexts.get(0).transform.getXrotation(); - + // Component exporting String groupName = component.getName() + "_" + idx; handleComponent(obj, this.configuration, this.transformer, component, groupName, this.LOD); - // TODO: motor rendering - idx++; } @@ -184,8 +185,15 @@ public class OBJExporterFactory { throw new IllegalArgumentException("Unsupported component type: " + component.getClass().getName()); } + // Export component final RocketComponentExporter exporter = factory.create(obj, config, transformer, component, groupName, LOD); exporter.addToObj(); + + // Export motor + if (component instanceof MotorMount) { + MotorExporter motorExporter = new MotorExporter(obj, config, transformer, component, groupName, LOD); + motorExporter.addToObj(); + } } interface ExporterFactory { diff --git a/core/src/net/sf/openrocket/file/wavefrontobj/export/components/MotorExporter.java b/core/src/net/sf/openrocket/file/wavefrontobj/export/components/MotorExporter.java new file mode 100644 index 000000000..b4cac22bb --- /dev/null +++ b/core/src/net/sf/openrocket/file/wavefrontobj/export/components/MotorExporter.java @@ -0,0 +1,136 @@ +package net.sf.openrocket.file.wavefrontobj.export.components; + +import net.sf.openrocket.file.wavefrontobj.CoordTransform; +import net.sf.openrocket.file.wavefrontobj.DefaultObj; +import net.sf.openrocket.file.wavefrontobj.DefaultObjFace; +import net.sf.openrocket.file.wavefrontobj.ObjUtils; +import net.sf.openrocket.file.wavefrontobj.export.shapes.CylinderExporter; +import net.sf.openrocket.file.wavefrontobj.export.shapes.DiskExporter; +import net.sf.openrocket.motor.Motor; +import net.sf.openrocket.motor.MotorConfiguration; +import net.sf.openrocket.rocketcomponent.FlightConfiguration; +import net.sf.openrocket.rocketcomponent.InstanceContext; +import net.sf.openrocket.rocketcomponent.MotorMount; +import net.sf.openrocket.rocketcomponent.RocketComponent; +import net.sf.openrocket.util.Coordinate; + +import java.util.ArrayList; +import java.util.List; + +public class MotorExporter { + protected final DefaultObj obj; + protected final FlightConfiguration config; + protected final RocketComponent mount; + protected final String groupName; + protected final ObjUtils.LevelOfDetail LOD; + protected final CoordTransform transformer; + + /** + * Wavefront OBJ exporter for a rocket component. + * + * @param obj The OBJ to export to + * @param config The flight configuration to use for the export + * @param transformer Coordinate system transformer to use to switch from the OpenRocket coordinate system to a custom OBJ coordinate system + * @param mount The motor mount that holds the motor to export + * @param groupName The name of the group to export to + * @param LOD Level of detail to use for the export (e.g. '80') + */ + public MotorExporter(DefaultObj obj, FlightConfiguration config, CoordTransform transformer, RocketComponent mount, + String groupName, ObjUtils.LevelOfDetail LOD) { + if (!(mount instanceof MotorMount)) { + throw new IllegalArgumentException("Motor exporter can only be used for motor mounts"); + } + this.obj = obj; + this.config = config; + this.transformer = transformer; + this.mount = mount; + this.groupName = groupName; + this.LOD = LOD; + } + + public void addToObj() { + MotorConfiguration motoConfig = ((MotorMount) mount).getMotorConfig(config.getId()); + Motor motor = motoConfig.getMotor(); + + if (motor == null) { + return; + } + + obj.setActiveGroupNames(motor.getMotorName()); + + for (InstanceContext context : config.getActiveInstances().getInstanceContexts(mount)) { + generateMesh(motor, context); + } + } + + private void generateMesh(Motor motor, InstanceContext context) { + final double length = motor.getLength(); + final double radius = motor.getDiameter() / 2; + final float coneLength = (float) (0.05 * length); // Length of the indent cone at the aft end of the motor + final int numSides = LOD.getNrOfSides(radius); + final int startIdx = obj.getNumVertices(); + + // Draw the cylinder + List foreRingVertices = new ArrayList<>(); + List aftRingVertices = new ArrayList<>(); + CylinderExporter.addCylinderMesh(obj, transformer, null, (float) radius, (float) length, numSides, false, true, + foreRingVertices, aftRingVertices); + + // Close the fore end + DiskExporter.closeDiskMesh(obj, transformer, null, foreRingVertices, false, true); + + // Generate the aft end inner ring vertices + List aftInnerRingVertices = new ArrayList<>(); + final int normalsStartIdx = obj.getNumNormals(); + final float innerRadius = (float) (0.8 * radius); + for (int i = 0; i < numSides; i++) { + final double angle = 2.0f * Math.PI * i / numSides; + final float x = (float) length; + final float y = (float) (innerRadius * Math.cos(angle)); + final float z = (float) (innerRadius * Math.sin(angle)); + obj.addVertex(transformer.convertLoc(x, y, z)); + + final double slopeAngle = Math.atan(coneLength / innerRadius); + final float nx = (float) Math.cos(slopeAngle); + final float ny = (float) -Math.cos(angle); + final float nz = (float) -Math.sin(angle); + obj.addNormal(transformer.convertLocWithoutOriginOffs(nx, ny, nz)); + + aftInnerRingVertices.add(obj.getNumVertices() - 1); + } + + // Close outer and inner aft ring + DiskExporter.closeDiskMesh(obj, transformer, null, aftRingVertices, aftInnerRingVertices, false, false); + + // Add cone tip vertex + obj.addVertex(transformer.convertLoc(length - coneLength, 0, 0)); + obj.addNormal(transformer.convertLocWithoutOriginOffs(1, 0, 0)); + + int endIdx = Math.max(obj.getNumVertices() - 1, startIdx); // Clamp in case no vertices were added + int normalsEndIdx = Math.max(obj.getNumNormals() - 1, normalsStartIdx); + + // Create the cone faces + for (int i = 0; i < numSides; i++) { + final int nextIdx = (i + 1) % numSides; + final int[] vertexIndices = new int[] { + endIdx, + aftInnerRingVertices.get(nextIdx), + aftInnerRingVertices.get(i), + }; + final int[] normalIndices = new int[] { + normalsEndIdx, + normalsStartIdx + nextIdx, + normalsStartIdx + i, + }; + + DefaultObjFace face = new DefaultObjFace(vertexIndices, null, normalIndices); + obj.addFace(face); + } + + + // Translate the mesh to the position in the rocket + Coordinate location = context.getLocation(); + location = location.add(mount.getLength() - length, 0, 0); // Motor starts at the aft end of the mount + ObjUtils.translateVerticesFromComponentLocation(obj, transformer, startIdx, endIdx, location); + } +} 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 0766ac320..cf7402d82 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 @@ -129,27 +129,27 @@ public class CylinderExporter { } public static void addCylinderMesh(@NotNull DefaultObj obj, @NotNull CoordTransform transformer, String groupName, - float radius, float height, int numSides, boolean solid, + float radius, float length, int numSides, boolean solid, List foreRingVertices, List aftRingVertices) { - addCylinderMesh(obj, transformer, groupName, radius, height, numSides, solid, true, foreRingVertices, aftRingVertices); + addCylinderMesh(obj, transformer, groupName, radius, length, numSides, solid, true, foreRingVertices, aftRingVertices); } public static void addCylinderMesh(@NotNull DefaultObj obj, @NotNull CoordTransform transformer, String groupName, - float radius, float height, boolean solid, ObjUtils.LevelOfDetail LOD, + float radius, float length, boolean solid, ObjUtils.LevelOfDetail LOD, List foreRingVertices, List aftRingVertices) { - addCylinderMesh(obj, transformer, groupName, radius, height, LOD.getNrOfSides(radius), solid, foreRingVertices, aftRingVertices); + addCylinderMesh(obj, transformer, groupName, radius, length, LOD.getNrOfSides(radius), solid, foreRingVertices, aftRingVertices); } public static void addCylinderMesh(@NotNull DefaultObj obj, @NotNull CoordTransform transformer, String groupName, - float radius, float height, boolean solid, boolean isOutside, int nrOfSlices, + float radius, float length, boolean solid, boolean isOutside, int nrOfSlices, List foreRingVertices, List aftRingVertices) { - addCylinderMesh(obj, transformer, groupName, radius, height, nrOfSlices, solid, isOutside, foreRingVertices, aftRingVertices); + addCylinderMesh(obj, transformer, groupName, radius, length, nrOfSlices, solid, isOutside, foreRingVertices, aftRingVertices); } public static void addCylinderMesh(@NotNull DefaultObj obj, @NotNull CoordTransform transformer, String groupName, - float radius, float height, boolean solid, int nrOfSlices, + float radius, float length, boolean solid, int nrOfSlices, List foreRingVertices, List aftRingVertices) { - addCylinderMesh(obj, transformer, groupName, radius, height, nrOfSlices, solid, foreRingVertices, aftRingVertices); + addCylinderMesh(obj, transformer, groupName, radius, length, nrOfSlices, solid, foreRingVertices, aftRingVertices); } public static void generateRingVertices(DefaultObj obj, CoordTransform transformer,