Browse Source

Refactored Cylinder generation to be more maintainable, readable, and fix #640.

stophe 8 năm trước cách đây
mục cha
commit
edf279a06d
1 tập tin đã thay đổi với 225 bổ sung184 xóa
  1. 225 184
      jme3-core/src/main/java/com/jme3/scene/shape/Cylinder.java

+ 225 - 184
jme3-core/src/main/java/com/jme3/scene/shape/Cylinder.java

@@ -40,14 +40,11 @@ import com.jme3.math.FastMath;
 import com.jme3.math.Vector3f;
 import com.jme3.scene.Mesh;
 import com.jme3.scene.VertexBuffer.Type;
-import com.jme3.scene.mesh.IndexBuffer;
 import com.jme3.util.BufferUtils;
-import static com.jme3.util.BufferUtils.*;
 import java.io.IOException;
-import java.nio.FloatBuffer;
 
 /**
- * A simple cylinder, defined by it's height and radius.
+ * A simple cylinder, defined by its height and radius.
  * (Ported to jME3)
  *
  * @author Mark Powell
@@ -127,10 +124,10 @@ public class Cylinder extends Mesh {
      * mapped to texture coordinates (0.5, 1), bottom to (0.5, 0). Thus you need
      * a suited distorted texture.
      *
-     * @param axisSamples
-     *            Number of triangle samples along the axis.
-     * @param radialSamples
-     *            Number of triangle samples along the radial.
+     * @param axisSamples The number of vertices samples along the axis. It is equal to the number of segments + 1; so 
+	 * that, for instance, 4 samples mean the cylinder will be made of 3 segments.
+     * @param radialSamples The number of triangle samples along the radius. For instance, 4 means that the sides of the
+	 * cylinder are made of 4 rectangles, and the top and bottom are made of 4 triangles.
      * @param radius
      *            The radius of the cylinder.
      * @param height
@@ -201,194 +198,240 @@ public class Cylinder extends Mesh {
     /**
      * Rebuilds the cylinder based on a new set of parameters.
      *
-     * @param axisSamples the number of samples along the axis.
-     * @param radialSamples the number of samples around the radial.
-     * @param radius the radius of the bottom of the cylinder.
-     * @param radius2 the radius of the top of the cylinder.
+     * @param axisSamples The number of vertices samples along the axis. It is equal to the number of segments + 1; so 
+	 * that, for instance, 4 samples mean the cylinder will be made of 3 segments.
+     * @param radialSamples The number of triangle samples along the radius. For instance, 4 means that the sides of the
+	 * cylinder are made of 4 rectangles, and the top and bottom are made of 4 triangles.
+     * @param topRadius the radius of the top of the cylinder.
+	 * @param bottomRadius the radius of the bottom of the cylinder.
      * @param height the cylinder's height.
      * @param closed should the cylinder have top and bottom surfaces.
      * @param inverted is the cylinder is meant to be viewed from the inside.
      */
     public void updateGeometry(int axisSamples, int radialSamples,
-            float radius, float radius2, float height, boolean closed, boolean inverted) {
-        this.axisSamples = axisSamples;
+            float topRadius, float bottomRadius, float height, boolean closed, boolean inverted)
+	{
+		// Ensure there's at least two axis samples and 3 radial samples, and positive geometries.
+		if( axisSamples < 2
+			|| radialSamples < 3
+			|| topRadius <= 0
+			|| bottomRadius <= 0
+			|| height <= 0 )
+			return;
+		
+		this.axisSamples = axisSamples;
         this.radialSamples = radialSamples;
-        this.radius = radius;
-        this.radius2 = radius2;
+        this.radius = bottomRadius;
+        this.radius2 = topRadius;
         this.height = height;
         this.closed = closed;
         this.inverted = inverted;
 
-//        VertexBuffer pvb = getBuffer(Type.Position);
-//        VertexBuffer nvb = getBuffer(Type.Normal);
-//        VertexBuffer tvb = getBuffer(Type.TexCoord);
-        axisSamples += (closed ? 2 : 0);
-
-        // Vertices
-        int vertCount = axisSamples * (radialSamples + 1) + (closed ? 2 : 0);
-
-        setBuffer(Type.Position, 3, createVector3Buffer(getFloatBuffer(Type.Position), vertCount));
-
-        // Normals
-        setBuffer(Type.Normal, 3, createVector3Buffer(getFloatBuffer(Type.Normal), vertCount));
-
-        // Texture co-ordinates
-        setBuffer(Type.TexCoord, 2, createVector2Buffer(vertCount));
-
-        int triCount = ((closed ? 2 : 0) + 2 * (axisSamples - 1)) * radialSamples;
-        
-        setBuffer(Type.Index, 3, createShortBuffer(getShortBuffer(Type.Index), 3 * triCount));
-
-        // generate geometry
-        float inverseRadial = 1.0f / radialSamples;
-        float inverseAxisLess = 1.0f / (closed ? axisSamples - 3 : axisSamples - 1);
-        float inverseAxisLessTexture = 1.0f / (axisSamples - 1);
-        float halfHeight = 0.5f * height;
-
-        // Generate points on the unit circle to be used in computing the mesh
-        // points on a cylinder slice.
-        float[] sin = new float[radialSamples + 1];
-        float[] cos = new float[radialSamples + 1];
-
-        for (int radialCount = 0; radialCount < radialSamples; radialCount++) {
-            float angle = FastMath.TWO_PI * inverseRadial * radialCount;
-            cos[radialCount] = FastMath.cos(angle);
-            sin[radialCount] = FastMath.sin(angle);
+        // Vertices : One per radial sample plus one duplicate for texture closing around the sides.
+        int verticesCount = axisSamples * (radialSamples +1);
+		// Triangles: Two per side rectangle, which is the product of numbers of samples.
+		int trianglesCount = axisSamples * radialSamples * 2 ;
+		if( closed )
+		{
+			// If there are caps, add two additional rims and two summits.
+			verticesCount += 2 + 2 * (radialSamples +1);
+			// Add one triangle per radial sample, twice, to form the caps.
+			trianglesCount += 2 * radialSamples ;
+		}
+
+		// Compute the points along a unit circle:
+		float[][] circlePoints = new float[radialSamples+1][2];
+		for (int circlePoint = 0; circlePoint < radialSamples; circlePoint++)
+		{
+            float angle = FastMath.TWO_PI / radialSamples * circlePoint;
+            circlePoints[circlePoint][0] = FastMath.cos(angle);
+            circlePoints[circlePoint][1] = FastMath.sin(angle);
         }
-        sin[radialSamples] = sin[0];
-        cos[radialSamples] = cos[0];
-
-        // calculate normals
-        Vector3f[] vNormals = null;
-        Vector3f vNormal = Vector3f.UNIT_Z;
-
-        if ((height != 0.0f) && (radius != radius2)) {
-            vNormals = new Vector3f[radialSamples];
-            Vector3f vHeight = Vector3f.UNIT_Z.mult(height);
-            Vector3f vRadial = new Vector3f();
-
-            for (int radialCount = 0; radialCount < radialSamples; radialCount++) {
-                vRadial.set(cos[radialCount], sin[radialCount], 0.0f);
-                Vector3f vRadius = vRadial.mult(radius);
-                Vector3f vRadius2 = vRadial.mult(radius2);
-                Vector3f vMantle = vHeight.subtract(vRadius2.subtract(vRadius));
-                Vector3f vTangent = vRadial.cross(Vector3f.UNIT_Z);
-                vNormals[radialCount] = vMantle.cross(vTangent).normalize();
-            }
-        }
-
-        FloatBuffer nb = getFloatBuffer(Type.Normal);
-        FloatBuffer pb = getFloatBuffer(Type.Position);
-        FloatBuffer tb = getFloatBuffer(Type.TexCoord);
-
-        // generate the cylinder itself
-        Vector3f tempNormal = new Vector3f();
-        for (int axisCount = 0, i = 0; axisCount < axisSamples; axisCount++, i++) {
-            float axisFraction;
-            float axisFractionTexture;
-            int topBottom = 0;
-            if (!closed) {
-                axisFraction = axisCount * inverseAxisLess; // in [0,1]
-                axisFractionTexture = axisFraction;
-            } else {
-                if (axisCount == 0) {
-                    topBottom = -1; // bottom
-                    axisFraction = 0;
-                    axisFractionTexture = inverseAxisLessTexture;
-                } else if (axisCount == axisSamples - 1) {
-                    topBottom = 1; // top
-                    axisFraction = 1;
-                    axisFractionTexture = 1 - inverseAxisLessTexture;
-                } else {
-                    axisFraction = (axisCount - 1) * inverseAxisLess;
-                    axisFractionTexture = axisCount * inverseAxisLessTexture;
-                }
-            }
-
-            // compute center of slice
-            float z = -halfHeight + height * axisFraction;
-            Vector3f sliceCenter = new Vector3f(0, 0, z);
-
-            // compute slice vertices with duplication at end point
-            int save = i;
-            for (int radialCount = 0; radialCount < radialSamples; radialCount++, i++) {
-                float radialFraction = radialCount * inverseRadial; // in [0,1)
-                tempNormal.set(cos[radialCount], sin[radialCount], 0.0f);
-
-                if (vNormals != null) {
-                    vNormal = vNormals[radialCount];
-                } else if (radius == radius2) {
-                    vNormal = tempNormal;
-                }
-
-                if (topBottom == 0) {
-                    if (!inverted)
-                        nb.put(vNormal.x).put(vNormal.y).put(vNormal.z);
-                    else
-                        nb.put(-vNormal.x).put(-vNormal.y).put(-vNormal.z);
-                } else {
-                    nb.put(0).put(0).put(topBottom * (inverted ? -1 : 1));
-                }
-
-                tempNormal.multLocal((radius - radius2) * axisFraction + radius2)
-                        .addLocal(sliceCenter);
-                pb.put(tempNormal.x).put(tempNormal.y).put(tempNormal.z);
-
-                tb.put((inverted ? 1 - radialFraction : radialFraction))
-                        .put(axisFractionTexture);
-            }
-
-            BufferUtils.copyInternalVector3(pb, save, i);
-            BufferUtils.copyInternalVector3(nb, save, i);
-
-            tb.put((inverted ? 0.0f : 1.0f))
-                    .put(axisFractionTexture);
+		// Add an additional point for closing the texture around the side of the cylinder.
+		circlePoints[radialSamples][0] = circlePoints[0][0];
+        circlePoints[radialSamples][1] = circlePoints[0][1];
+		
+        // Calculate normals.
+		// 
+		// A---------B
+		//  \        |
+		//   \       |
+		//    \      |
+		//     D-----C
+		//
+		// Let be B and C the top and bottom points of the axis, and A and D the top and bottom edges.
+		// The normal in A and D is simply orthogonal to AD, which means we can get it once per sample.
+		//
+		Vector3f[] circleNormals = new Vector3f[radialSamples+1];
+		for (int circlePoint = 0; circlePoint < radialSamples+1; circlePoint++)
+		{
+            // The normal is the orthogonal to the side, which can be got without trigonometry.
+			// The edge direction is oriented so that it goes up by Height, and out by the radius difference; let's use
+			// those values in reverse order.
+			Vector3f normal = new Vector3f(height * circlePoints[circlePoint][0], height * circlePoints[circlePoint][1], bottomRadius - topRadius );
+			circleNormals[circlePoint] = normal.normalizeLocal();
         }
 
-        if (closed) {
-            pb.put(0).put(0).put(-halfHeight); // bottom center
-            nb.put(0).put(0).put(-1 * (inverted ? -1 : 1));
-            tb.put(0.5f).put(0);
-            pb.put(0).put(0).put(halfHeight); // top center
-            nb.put(0).put(0).put(1 * (inverted ? -1 : 1));
-            tb.put(0.5f).put(1);
-        }
-
-        IndexBuffer ib = getIndexBuffer();
-        int index = 0;
-        // Connectivity
-        for (int axisCount = 0, axisStart = 0; axisCount < axisSamples - 1; axisCount++) {
-            int i0 = axisStart;
-            int i1 = i0 + 1;
-            axisStart += radialSamples + 1;
-            int i2 = axisStart;
-            int i3 = i2 + 1;
-            for (int i = 0; i < radialSamples; i++) {
-                if (closed && axisCount == 0) {
-                    if (!inverted) {
-                        ib.put(index++, i0++);
-                        ib.put(index++, vertCount - 2);
-                        ib.put(index++, i1++);
-                    } else {
-                        ib.put(index++, i0++);
-                        ib.put(index++, i1++);
-                        ib.put(index++, vertCount - 2);
-                    }
-                } else if (closed && axisCount == axisSamples - 2) {
-                    ib.put(index++, i2++);
-                    ib.put(index++, inverted ? vertCount - 1 : i3++);
-                    ib.put(index++, inverted ? i3++ : vertCount - 1);
-                } else {
-                    ib.put(index++, i0++);
-                    ib.put(index++, inverted ? i2 : i1);
-                    ib.put(index++, inverted ? i1 : i2);
-                    ib.put(index++, i1++);
-                    ib.put(index++, inverted ? i2++ : i3++);
-                    ib.put(index++, inverted ? i3++ : i2++);
-                }
-            }
+		float[] vertices = new float[verticesCount * 3];
+		float[] normals = new float[verticesCount * 3];
+		float[] textureCoords = new float[verticesCount * 2];
+		int currentIndex = 0;
+		
+		// Add a circle of points for each axis sample.
+		for(int axisSample = 0; axisSample < axisSamples; axisSample++ )
+		{
+			float currentHeight = -height / 2 + height * axisSample / (axisSamples-1);
+			float currentRadius = bottomRadius + (topRadius - bottomRadius) * axisSample / (axisSamples-1);
+			
+			for (int circlePoint = 0; circlePoint < radialSamples + 1; circlePoint++)
+			{
+				// Position, by multipliying the position on a unit circle with the current radius.
+				vertices[currentIndex*3] = circlePoints[circlePoint][0] * currentRadius;
+				vertices[currentIndex*3 +1] = circlePoints[circlePoint][1] * currentRadius;
+				vertices[currentIndex*3 +2] = currentHeight;
+				
+				// Normal
+				Vector3f currentNormal = circleNormals[circlePoint];
+				normals[currentIndex*3] = currentNormal.x;
+				normals[currentIndex*3+1] = currentNormal.y;
+				normals[currentIndex*3+2] = currentNormal.z;
+						
+				// Texture
+				// The X is the angular position of the point.
+				textureCoords[currentIndex *2] = (float) circlePoint / radialSamples;
+				// Depending on whether there is a cap, the Y is either the height scaled to [0,1], or the radii of 
+				// the cap count as well.
+				if (closed)
+					textureCoords[currentIndex *2 +1] = (bottomRadius + height / 2 + currentHeight) / (bottomRadius + height + topRadius);
+				else
+					textureCoords[currentIndex *2 +1] = height / 2 + currentHeight;
+				
+				currentIndex++;
+			}
+		}
+		
+		// If closed, add duplicate rims on top and bottom, with normals facing up and down.
+		if (closed)
+		{
+			// Bottom
+			for (int circlePoint = 0; circlePoint < radialSamples + 1; circlePoint++)
+			{
+				vertices[currentIndex*3] = circlePoints[circlePoint][0] * bottomRadius;
+				vertices[currentIndex*3 +1] = circlePoints[circlePoint][1] * bottomRadius;
+				vertices[currentIndex*3 +2] = -height/2;
+
+				normals[currentIndex*3] = 0;
+				normals[currentIndex*3+1] = 0;
+				normals[currentIndex*3+2] = -1;
+
+				textureCoords[currentIndex *2] = (float) circlePoint / radialSamples;
+				textureCoords[currentIndex *2 +1] = bottomRadius / (bottomRadius + height + topRadius);
+
+				currentIndex++;
+			}
+			// Top
+			for (int circlePoint = 0; circlePoint < radialSamples + 1; circlePoint++)
+			{
+				vertices[currentIndex*3] = circlePoints[circlePoint][0] * topRadius;
+				vertices[currentIndex*3 +1] = circlePoints[circlePoint][1] * topRadius;
+				vertices[currentIndex*3 +2] = height/2;
+
+				normals[currentIndex*3] = 0;
+				normals[currentIndex*3+1] = 0;
+				normals[currentIndex*3+2] = 1;
+
+				textureCoords[currentIndex *2] = (float) circlePoint / radialSamples;
+				textureCoords[currentIndex *2 +1] = (bottomRadius + height) / (bottomRadius + height + topRadius);
+
+				currentIndex++;
+			}
+			
+			// Add the centers of the caps.
+			vertices[currentIndex*3] = 0;
+			vertices[currentIndex*3 +1] = 0;
+			vertices[currentIndex*3 +2] = -height/2;
+			
+			normals[currentIndex*3] = 0;
+			normals[currentIndex*3+1] = 0;
+			normals[currentIndex*3+2] = -1;
+			
+			textureCoords[currentIndex *2] = 0.5f;
+			textureCoords[currentIndex *2+1] = 0f;
+			
+			currentIndex++;
+			
+			vertices[currentIndex*3] = 0;
+			vertices[currentIndex*3 +1] = 0;
+			vertices[currentIndex*3 +2] = height/2;
+			
+			normals[currentIndex*3] = 0;
+			normals[currentIndex*3+1] = 0;
+			normals[currentIndex*3+2] = 1;
+			
+			textureCoords[currentIndex *2] = 0.5f;
+			textureCoords[currentIndex *2+1] = 1f;
         }
 
+		// Add the triangles indexes.
+        short[] indices = new short[trianglesCount * 3];
+		currentIndex = 0;
+		for (short axisSample = 0; axisSample < axisSamples - 1; axisSample++)
+		{
+			for (int circlePoint = 0; circlePoint < radialSamples; circlePoint++)
+			{
+				indices[currentIndex++] = (short) (axisSample * (radialSamples + 1) + circlePoint);
+				indices[currentIndex++] =  (short) (axisSample * (radialSamples + 1) + circlePoint + 1);
+				indices[currentIndex++] =  (short) ((axisSample + 1) * (radialSamples + 1) + circlePoint);
+
+				indices[currentIndex++] =  (short) ((axisSample + 1) * (radialSamples + 1) + circlePoint);
+				indices[currentIndex++] =  (short) (axisSample * (radialSamples + 1) + circlePoint + 1);
+				indices[currentIndex++] =  (short) ((axisSample + 1) * (radialSamples + 1) + circlePoint + 1);
+			}
+		}
+		// Add caps if needed.
+		if(closed)
+		{
+			short bottomCapIndex = (short) (verticesCount - 2);
+			short topCapIndex = (short) (verticesCount - 1);
+			
+			int bottomRowOffset = (axisSamples) * (radialSamples +1 );
+			int topRowOffset = (axisSamples+1) * (radialSamples +1 );
+			
+			for (int circlePoint = 0; circlePoint < radialSamples; circlePoint++)
+			{
+				indices[currentIndex++] =  (short) (bottomRowOffset + circlePoint +1);
+				indices[currentIndex++] = (short) (bottomRowOffset + circlePoint);
+				indices[currentIndex++] =  bottomCapIndex;
+
+				
+				indices[currentIndex++] = (short) (topRowOffset + circlePoint);
+				indices[currentIndex++] =  (short) (topRowOffset + circlePoint +1);
+				indices[currentIndex++] =  topCapIndex;
+			}
+		}
+
+		// If inverted, the triangles and normals are all reverted.
+		if (inverted)
+		{
+			for (int i = 0; i < indices.length / 2; i++)
+			{
+				short temp = indices[i];
+				indices[i] = indices[indices.length - 1 - i];
+				indices[indices.length - 1 - i] = temp;
+			}
+			
+			for(int i = 0; i< normals.length; i++)
+			{
+				normals[i] = -normals[i];
+			}
+		}
+		
+		// Fill in the buffers.
+		setBuffer(Type.Position, 3, BufferUtils.createFloatBuffer(vertices));
+		setBuffer(Type.Normal, 3, BufferUtils.createFloatBuffer(normals));
+		setBuffer(Type.TexCoord, 2, BufferUtils.createFloatBuffer(textureCoords));
+        setBuffer(Type.Index, 3, BufferUtils.createShortBuffer(indices));
+		
         updateBound();
         setStatic();
     }
@@ -418,6 +461,4 @@ public class Cylinder extends Mesh {
         capsule.write(closed, "closed", false);
         capsule.write(inverted, "inverted", false);
     }
-
-
-}
+}