diff --git a/jme3-core/src/main/java/com/jme3/scene/shape/Cylinder.java b/jme3-core/src/main/java/com/jme3/scene/shape/Cylinder.java index 1e0b1cae2..bc8bf92b1 100644 --- a/jme3-core/src/main/java/com/jme3/scene/shape/Cylinder.java +++ b/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); } - - -} +} \ No newline at end of file