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 index e742d3152..cbf07bff9 100644 --- a/core/src/net/sf/openrocket/file/wavefrontobj/export/components/MotorExporter.java +++ b/core/src/net/sf/openrocket/file/wavefrontobj/export/components/MotorExporter.java @@ -74,10 +74,10 @@ public class MotorExporter { List foreRingVertices = new ArrayList<>(); List aftRingVertices = new ArrayList<>(); CylinderExporter.addCylinderMesh(obj, transformer, null, (float) radius, (float) length, numSides, false, true, - foreRingVertices, aftRingVertices); + 0, 1, 0.125f, 0.875f, foreRingVertices, aftRingVertices); // Close the fore end - DiskExporter.closeDiskMesh(obj, transformer, null, foreRingVertices, false, true); + DiskExporter.closeDiskMesh(obj, transformer, null, foreRingVertices, false, true, 0, 1, 0.875f, 1); // Generate the aft end inner ring vertices List aftInnerRingVertices = new ArrayList<>(); @@ -100,12 +100,26 @@ public class MotorExporter { } // Close outer and inner aft ring - DiskExporter.closeDiskMesh(obj, transformer, null, aftRingVertices, aftInnerRingVertices, false, false); + DiskExporter.closeDiskMesh(obj, transformer, null, aftRingVertices, aftInnerRingVertices, false, false, 0, 1, 0.125f, 0.1f); // Add cone tip vertex obj.addVertex(transformer.convertLoc(length - coneLength, 0, 0)); obj.addNormal(transformer.convertLocWithoutOriginOffs(1, 0, 0)); + // Add texture coordinates + final int texCoordsStartIdx = obj.getNumTexCoords(); + //// Inner aft ring + for (int i = 0; i <= numSides; i++) { + final float u = ((float) i) / numSides; + obj.addTexCoord(u, 0.1f); + } + + //// Cone tip + for (int i = 0; i <= numSides; i++) { + final float u = ((float) i) / numSides; + obj.addTexCoord(u, 0f); + } + int endIdx = Math.max(obj.getNumVertices() - 1, startIdx); // Clamp in case no vertices were added int normalsEndIdx = Math.max(obj.getNumNormals() - 1, normalsStartIdx); @@ -122,8 +136,14 @@ public class MotorExporter { normalsStartIdx + nextIdx, normalsStartIdx + i, }; + final int[] texCoordIndices = new int[] { + numSides+1 + i, + i+1, + i, + }; + ObjUtils.offsetIndex(texCoordIndices, texCoordsStartIdx); - DefaultObjFace face = new DefaultObjFace(vertexIndices, null, normalIndices); + DefaultObjFace face = new DefaultObjFace(vertexIndices, texCoordIndices, normalIndices); obj.addFace(face); } 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 03ed31936..fc833ef24 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 @@ -5,6 +5,7 @@ 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.util.MathUtil; import java.util.List; @@ -22,6 +23,10 @@ public class CylinderExporter { * NOTE: Culling is not really thought of for the hollow cylinder; this mode is really meant to be * combined with other objects * @param isOutside Whether the cylinder is an outside face (true) or inside face (false) + * @param uMin The minimum u texture coordinate + * @param uMax The maximum u texture coordinate + * @param vMin The minimum v texture coordinate + * @param vMax The maximum v texture coordinate * @param foreRingVertices A list to add the fore (top) ring vertex indices to * @param aftRingVertices A list to add the aft (bottom) ring vertex indices to * @param foreRingNormals A list to add the fore (top) ring normal indices to @@ -29,6 +34,7 @@ public class CylinderExporter { */ public static void addCylinderMesh(@NotNull DefaultObj obj, @NotNull CoordTransform transformer, String groupName, float foreRadius, float aftRadius, float length, int numSides, boolean solid, boolean isOutside, + float uMin, float uMax, float vMin, float vMax, List foreRingVertices, List aftRingVertices, List foreRingNormals, List aftRingNormals) { // Set the new group @@ -52,10 +58,12 @@ public class CylinderExporter { } // Generate side top vertices - generateRingVertices(obj, transformer, numSides, 0, length, length, foreRadius, aftRadius, isOutside, foreRingVertices, foreRingNormals); + generateRingVertices(obj, transformer, numSides, 0, length, length, foreRadius, aftRadius, isOutside, + uMin, uMax, vMin, vMax, foreRingVertices, foreRingNormals); // Generate side bottom vertices - generateRingVertices(obj, transformer, numSides, length, 0, length, aftRadius, foreRadius, isOutside, aftRingVertices, aftRingNormals); + generateRingVertices(obj, transformer, numSides, length, 0, length, aftRadius, foreRadius, isOutside, + uMin, uMax, vMin, vMax, aftRingVertices, aftRingNormals); // Create faces for the bottom and top if (solid) { @@ -138,6 +146,21 @@ public class CylinderExporter { obj.addFace(face); } } + public static void addCylinderMesh(@NotNull DefaultObj obj, @NotNull CoordTransform transformer, String groupName, + float foreRadius, float aftRadius, float length, int numSides, boolean solid, boolean isOutside, + List foreRingVertices, List aftRingVertices, + List foreRingNormals, List aftRingNormals) { + addCylinderMesh(obj, transformer, groupName, foreRadius, aftRadius, length, numSides, solid, isOutside, + 0, 1, 0, 1, foreRingVertices, aftRingVertices, foreRingNormals, aftRingNormals); + } + + public static void addCylinderMesh(@NotNull DefaultObj obj, @NotNull CoordTransform transformer, String groupName, + float foreRadius, float aftRadius, float length, int numSides, boolean solid, boolean isOutside, + float uMin, float uMax, float vMin, float vMax, + List foreRingVertices, List aftRingVertices) { + addCylinderMesh(obj, transformer, groupName, foreRadius, aftRadius, length, numSides, solid, isOutside, + uMin, uMax, vMin, vMax, foreRingVertices, aftRingVertices, null, null); + } public static void addCylinderMesh(@NotNull DefaultObj obj, @NotNull CoordTransform transformer, String groupName, float foreRadius, float aftRadius, float length, int numSides, boolean solid, boolean isOutside, @@ -146,6 +169,14 @@ public class CylinderExporter { foreRingVertices, aftRingVertices, null, null); } + public static void addCylinderMesh(@NotNull DefaultObj obj, @NotNull CoordTransform transformer, String groupName, + float radius, float length, int numSides, boolean solid, boolean isOutside, + float uMin, float uMax, float vMin, float vMax, + List foreRingVertices, List aftRingVertices) { + addCylinderMesh(obj, transformer, groupName, radius, radius, length, numSides, solid, isOutside, + uMin, uMax, vMin, vMax, foreRingVertices, aftRingVertices); + } + public static void addCylinderMesh(@NotNull DefaultObj obj, @NotNull CoordTransform transformer, String groupName, float radius, float length, int numSides, boolean solid, boolean isOutside, List foreRingVertices, List aftRingVertices) { @@ -172,7 +203,8 @@ public class CylinderExporter { public static void generateRingVertices(DefaultObj obj, CoordTransform transformer, int numSides, float x, float nextX, float xMax, float radius, float nextRadius, - boolean isOutside, List vertexList, List normalList) { + boolean isOutside, float uMin, float uMax, float vMin, float vMax, + List vertexList, List normalList) { int startIdx = obj.getNumVertices(); int normalsStartIdx = obj.getNumNormals(); @@ -181,10 +213,11 @@ public class CylinderExporter { final float y = radius * (float) Math.cos(angle); final float z = radius * (float) Math.sin(angle); - // Side top vertices + // Vertex obj.addVertex(transformer.convertLoc(x, y, z)); - // We need special nx normal when the radius changes + // Normal + //// We need special nx normal when the radius changes float nx; if (Float.compare(radius, nextRadius) != 0) { final double slopeAngle = Math.atan(Math.abs(nextX - x) / (nextRadius - radius)); @@ -209,13 +242,17 @@ public class CylinderExporter { } // Texture coordinates - final float u = (float) i / numSides; - final float v = isOutside ? (xMax - x) / xMax : x / xMax; // For some reason, the texture is vertically flipped in OR for inside cylinders. Don't really like it, but it is what it is + + float u = ((float) i) / numSides; + u = (float) MathUtil.map(u, 0, 1, uMin, uMax); + float v = isOutside ? (xMax - x) / xMax : x / xMax; // For some reason, the texture is vertically flipped in OR for inside cylinders. Don't really like it, but it is what it is + v = (float) MathUtil.map(v, 0, 1, vMin, vMax); obj.addTexCoord(u, v); } // Need to add a last texture coordinate for the end of the texture - final float v = isOutside ? (xMax - x) / xMax : x / xMax; - obj.addTexCoord(1f, v); + float v = isOutside ? (xMax - x) / xMax : x / xMax; + v = (float) MathUtil.map(v, 0, 1, vMin, vMax); + obj.addTexCoord(uMax, v); } } diff --git a/core/src/net/sf/openrocket/file/wavefrontobj/export/shapes/DiskExporter.java b/core/src/net/sf/openrocket/file/wavefrontobj/export/shapes/DiskExporter.java index b9008ea39..088c2c6f3 100644 --- a/core/src/net/sf/openrocket/file/wavefrontobj/export/shapes/DiskExporter.java +++ b/core/src/net/sf/openrocket/file/wavefrontobj/export/shapes/DiskExporter.java @@ -19,10 +19,15 @@ public class DiskExporter { * @param innerVertices The indices of the inner vertices, or null if the disk is solid * @param isClockwise Whether the vertices are in clockwise order (true) or counter-clockwise order (false) * @param isTopFace Whether the disk is a top face (true) or bottom face (false) + * @param uMin The minimum u texture coordinate + * @param uMax The maximum u texture coordinate + * @param vMin The minimum v texture coordinate + * @param vMax The maximum v texture coordinate */ public static void closeDiskMesh(@NotNull DefaultObj obj, @NotNull CoordTransform transformer, String groupName, @NotNull List outerVertices, List innerVertices, - boolean isClockwise, boolean isTopFace) { + boolean isClockwise, boolean isTopFace, + float uMin, float uMax, float vMin, float vMax) { if (outerVertices.isEmpty()) { throw new IllegalArgumentException("Outer vertices cannot be empty"); } @@ -45,6 +50,21 @@ public class DiskExporter { obj.addNormal(transformer.convertLocWithoutOriginOffs(isTopFace ? -1 : 1, 0, 0)); // TODO: hm, what if the object is rotated? If the disk is not drawn in the y direction? final int normalIndex = obj.getNumNormals() - 1; + final int texCoordsStartIdx = obj.getNumTexCoords(); + final int numSides = outerVertices.size(); + + // Create the texture coordinates + //// Outer vertices + for (int i = 0; i <= numSides; i++) { + final float u = uMin + ((float) i) / numSides * (uMax - uMin); + obj.addTexCoord(u, vMin); + } + //// Inner vertices + for (int i = 0; i <= numSides; i++) { + final float u = uMin + ((float) i) / numSides * (uMax - uMin); + obj.addTexCoord(u, vMax); + } + if (isSolid) { // Add the center vertex final int centerVertexIdx; @@ -59,6 +79,8 @@ public class DiskExporter { // Add the triangle faces for (int i = 0; i < outerVertices.size(); i++) { int nextIdx = (i + 1) % outerVertices.size(); + + // Vertices int[] vertexIndices = new int[] { centerVertexIdx, outerVertices.get(nextIdx), @@ -66,14 +88,27 @@ public class DiskExporter { }; vertexIndices = ObjUtils.reverseIndexWinding(vertexIndices, isTopFace != isClockwise); + // Normals int[] normalIndices = new int[] { normalIndex, normalIndex, normalIndex }; - DefaultObjFace face = new DefaultObjFace(vertexIndices, null, normalIndices); + + // Texture coordinates + int[] texCoordsIndices = new int[] { + numSides+1 + i, + i+1, + i, + }; + texCoordsIndices = ObjUtils.reverseIndexWinding(texCoordsIndices, isTopFace != isClockwise); + ObjUtils.offsetIndex(texCoordsIndices, texCoordsStartIdx); + + DefaultObjFace face = new DefaultObjFace(vertexIndices, texCoordsIndices, normalIndices); obj.addFace(face); } } else { // Add the quad faces for (int i = 0; i < outerVertices.size(); i++) { int nextIdx = (i + 1) % outerVertices.size(); + + // Vertices int[] vertexIndices = new int[] { outerVertices.get(i), // Bottom-left of quad innerVertices.get(i), // Top-left of quad @@ -82,13 +117,40 @@ public class DiskExporter { }; vertexIndices = ObjUtils.reverseIndexWinding(vertexIndices, isTopFace != isClockwise); + // Normals int[] normalIndices = new int[] { normalIndex, normalIndex, normalIndex, normalIndex }; - DefaultObjFace face = new DefaultObjFace(vertexIndices, null, normalIndices); + + // Texture coordinates + int[] texCoordsIndices = new int[] { + i, + numSides+1 + i, + numSides+1 + i+1, + i+1, + }; + texCoordsIndices = ObjUtils.reverseIndexWinding(texCoordsIndices, isTopFace != isClockwise); + ObjUtils.offsetIndex(texCoordsIndices, texCoordsStartIdx); + + + DefaultObjFace face = new DefaultObjFace(vertexIndices, texCoordsIndices, normalIndices); obj.addFace(face); } } } + public static void closeDiskMesh(@NotNull DefaultObj obj, @NotNull CoordTransform transformer, String groupName, + @NotNull List outerVertices, List innerVertices, + boolean isClockwise, boolean isTopFace) { + // By default, OpenRocket doesn't really render textures on disks (often edges of tubes), so we don't either + closeDiskMesh(obj, transformer, groupName, outerVertices, innerVertices, isClockwise, isTopFace, 0, 0, 0, 0); + } + + public static void closeDiskMesh(@NotNull DefaultObj obj, @NotNull CoordTransform transformer, String groupName, + @NotNull List outerVertices, boolean isClockwise, boolean isTopFace, + float uMin, float uMax, float vMin, float vMax) { + closeDiskMesh(obj, transformer, groupName, outerVertices, null, isClockwise, isTopFace, uMin, uMax, vMin, vMax); + } + + /** * Adds a (closed) disk mesh to the obj by using existing outer and inner vertices * @param obj The obj to add the mesh to @@ -100,7 +162,8 @@ public class DiskExporter { */ public static void closeDiskMesh(@NotNull DefaultObj obj, @NotNull CoordTransform transformer, String groupName, @NotNull List outerVertices, boolean isClockwise, boolean isTopFace) { - closeDiskMesh(obj, transformer, groupName, outerVertices, null, isClockwise, isTopFace); + // By default, OpenRocket doesn't really render textures on disks (often edges of tubes), so we don't either + closeDiskMesh(obj, transformer, groupName, outerVertices, isClockwise, isTopFace, 0, 0, 0, 0); } }