Added a unit test and fixed indentation.

fix-456
stophe 8 years ago
parent 61c22d5709
commit ebaad20f2f
  1. 393
      jme3-core/src/main/java/com/jme3/scene/shape/Cylinder.java
  2. 78
      jme3-core/src/test/java/com/jme3/scene/ShapeGeometryTest.java

@ -125,9 +125,9 @@ public class Cylinder extends Mesh {
* a suited distorted texture. * a suited distorted texture.
* *
* @param axisSamples The number of vertices samples along the axis. It is equal to the number of segments + 1; so * @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. * 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 * @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. * cylinder are made of 4 rectangles, and the top and bottom are made of 4 triangles.
* @param radius * @param radius
* The radius of the cylinder. * The radius of the cylinder.
* @param height * @param height
@ -199,27 +199,27 @@ public class Cylinder extends Mesh {
* Rebuilds the cylinder based on a new set of parameters. * Rebuilds the cylinder based on a new set of parameters.
* *
* @param axisSamples The number of vertices samples along the axis. It is equal to the number of segments + 1; so * @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. * 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 * @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. * 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 topRadius the radius of the top of the cylinder.
* @param bottomRadius the radius of the bottom of the cylinder. * @param bottomRadius the radius of the bottom of the cylinder.
* @param height the cylinder's height. * @param height the cylinder's height.
* @param closed should the cylinder have top and bottom surfaces. * @param closed should the cylinder have top and bottom surfaces.
* @param inverted is the cylinder is meant to be viewed from the inside. * @param inverted is the cylinder is meant to be viewed from the inside.
*/ */
public void updateGeometry(int axisSamples, int radialSamples, public void updateGeometry(int axisSamples, int radialSamples,
float topRadius, float bottomRadius, float height, boolean closed, boolean inverted) float topRadius, float bottomRadius, float height, boolean closed, boolean inverted) {
{ // Ensure there's at least two axis samples and 3 radial samples, and positive dimensions.
// Ensure there's at least two axis samples and 3 radial samples, and positive dimensions. if( axisSamples < 2
if( axisSamples < 2 || radialSamples < 3
|| radialSamples < 3 || topRadius <= 0
|| topRadius <= 0 || bottomRadius <= 0
|| bottomRadius <= 0 || height <= 0 ) {
|| height <= 0 ) throw new IllegalArgumentException("Cylinders must have at least 2 axis samples and 3 radial samples, and positive dimensions.");
throw new IllegalArgumentException("Cylinders must have at least 2 axis samples and 3 radial samples, and positive dimensions."); }
this.axisSamples = axisSamples; this.axisSamples = axisSamples;
this.radialSamples = radialSamples; this.radialSamples = radialSamples;
this.radius = bottomRadius; this.radius = bottomRadius;
this.radius2 = topRadius; this.radius2 = topRadius;
@ -229,207 +229,192 @@ public class Cylinder extends Mesh {
// Vertices : One per radial sample plus one duplicate for texture closing around the sides. // Vertices : One per radial sample plus one duplicate for texture closing around the sides.
int verticesCount = axisSamples * (radialSamples +1); int verticesCount = axisSamples * (radialSamples +1);
// Triangles: Two per side rectangle, which is the product of numbers of samples. // Triangles: Two per side rectangle, which is the product of numbers of samples.
int trianglesCount = axisSamples * radialSamples * 2 ; int trianglesCount = axisSamples * radialSamples * 2 ;
if( closed ) if( closed ) {
{ // If there are caps, add two additional rims and two summits.
// If there are caps, add two additional rims and two summits. verticesCount += 2 + 2 * (radialSamples +1);
verticesCount += 2 + 2 * (radialSamples +1); // Add one triangle per radial sample, twice, to form the caps.
// Add one triangle per radial sample, twice, to form the caps. trianglesCount += 2 * radialSamples ;
trianglesCount += 2 * radialSamples ; }
}
// Compute the points along a unit circle:
// Compute the points along a unit circle: float[][] circlePoints = new float[radialSamples+1][2];
float[][] circlePoints = new float[radialSamples+1][2]; for (int circlePoint = 0; circlePoint < radialSamples; circlePoint++) {
for (int circlePoint = 0; circlePoint < radialSamples; circlePoint++)
{
float angle = FastMath.TWO_PI / radialSamples * circlePoint; float angle = FastMath.TWO_PI / radialSamples * circlePoint;
circlePoints[circlePoint][0] = FastMath.cos(angle); circlePoints[circlePoint][0] = FastMath.cos(angle);
circlePoints[circlePoint][1] = FastMath.sin(angle); circlePoints[circlePoint][1] = FastMath.sin(angle);
} }
// Add an additional point for closing the texture around the side of the cylinder. // Add an additional point for closing the texture around the side of the cylinder.
circlePoints[radialSamples][0] = circlePoints[0][0]; circlePoints[radialSamples][0] = circlePoints[0][0];
circlePoints[radialSamples][1] = circlePoints[0][1]; circlePoints[radialSamples][1] = circlePoints[0][1];
// Calculate normals. // Calculate normals.
// //
// A---------B // A---------B
// \ | // \ |
// \ | // \ |
// \ | // \ |
// D-----C // D-----C
// //
// Let be B and C the top and bottom points of the axis, and A and D the top and bottom edges. // 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. // 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]; Vector3f[] circleNormals = new Vector3f[radialSamples+1];
for (int circlePoint = 0; circlePoint < radialSamples+1; circlePoint++) for (int circlePoint = 0; circlePoint < radialSamples+1; circlePoint++) {
{
// The normal is the orthogonal to the side, which can be got without trigonometry. // 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 // 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. // those values in reverse order.
Vector3f normal = new Vector3f(height * circlePoints[circlePoint][0], height * circlePoints[circlePoint][1], bottomRadius - topRadius ); Vector3f normal = new Vector3f(height * circlePoints[circlePoint][0], height * circlePoints[circlePoint][1], bottomRadius - topRadius );
circleNormals[circlePoint] = normal.normalizeLocal(); circleNormals[circlePoint] = normal.normalizeLocal();
} }
float[] vertices = new float[verticesCount * 3]; float[] vertices = new float[verticesCount * 3];
float[] normals = new float[verticesCount * 3]; float[] normals = new float[verticesCount * 3];
float[] textureCoords = new float[verticesCount * 2]; float[] textureCoords = new float[verticesCount * 2];
int currentIndex = 0; int currentIndex = 0;
// Add a circle of points for each axis sample. // Add a circle of points for each axis sample.
for(int axisSample = 0; axisSample < axisSamples; axisSample++ ) for(int axisSample = 0; axisSample < axisSamples; axisSample++ ) {
{ float currentHeight = -height / 2 + height * axisSample / (axisSamples-1);
float currentHeight = -height / 2 + height * axisSample / (axisSamples-1); float currentRadius = bottomRadius + (topRadius - bottomRadius) * axisSample / (axisSamples-1);
float currentRadius = bottomRadius + (topRadius - bottomRadius) * axisSample / (axisSamples-1);
for (int circlePoint = 0; circlePoint < radialSamples + 1; circlePoint++) {
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;
// Position, by multipliying the position on a unit circle with the current radius. vertices[currentIndex*3 +1] = circlePoints[circlePoint][1] * currentRadius;
vertices[currentIndex*3] = circlePoints[circlePoint][0] * currentRadius; vertices[currentIndex*3 +2] = currentHeight;
vertices[currentIndex*3 +1] = circlePoints[circlePoint][1] * currentRadius;
vertices[currentIndex*3 +2] = currentHeight; // Normal
Vector3f currentNormal = circleNormals[circlePoint];
// Normal normals[currentIndex*3] = currentNormal.x;
Vector3f currentNormal = circleNormals[circlePoint]; normals[currentIndex*3+1] = currentNormal.y;
normals[currentIndex*3] = currentNormal.x; normals[currentIndex*3+2] = currentNormal.z;
normals[currentIndex*3+1] = currentNormal.y;
normals[currentIndex*3+2] = currentNormal.z; // Texture
// The X is the angular position of the point.
// Texture textureCoords[currentIndex *2] = (float) circlePoint / radialSamples;
// The X is the angular position of the point. // Depending on whether there is a cap, the Y is either the height scaled to [0,1], or the radii of
textureCoords[currentIndex *2] = (float) circlePoint / radialSamples; // the cap count as well.
// Depending on whether there is a cap, the Y is either the height scaled to [0,1], or the radii of if (closed)
// the cap count as well. textureCoords[currentIndex *2 +1] = (bottomRadius + height / 2 + currentHeight) / (bottomRadius + height + topRadius);
if (closed) else
textureCoords[currentIndex *2 +1] = (bottomRadius + height / 2 + currentHeight) / (bottomRadius + height + topRadius); textureCoords[currentIndex *2 +1] = height / 2 + currentHeight;
else
textureCoords[currentIndex *2 +1] = height / 2 + currentHeight; currentIndex++;
}
currentIndex++; }
}
} // If closed, add duplicate rims on top and bottom, with normals facing up and down.
if (closed) {
// If closed, add duplicate rims on top and bottom, with normals facing up and down. // Bottom
if (closed) for (int circlePoint = 0; circlePoint < radialSamples + 1; circlePoint++) {
{ vertices[currentIndex*3] = circlePoints[circlePoint][0] * bottomRadius;
// Bottom vertices[currentIndex*3 +1] = circlePoints[circlePoint][1] * bottomRadius;
for (int circlePoint = 0; circlePoint < radialSamples + 1; circlePoint++) vertices[currentIndex*3 +2] = -height/2;
{
vertices[currentIndex*3] = circlePoints[circlePoint][0] * bottomRadius; normals[currentIndex*3] = 0;
vertices[currentIndex*3 +1] = circlePoints[circlePoint][1] * bottomRadius; normals[currentIndex*3+1] = 0;
vertices[currentIndex*3 +2] = -height/2; normals[currentIndex*3+2] = -1;
normals[currentIndex*3] = 0; textureCoords[currentIndex *2] = (float) circlePoint / radialSamples;
normals[currentIndex*3+1] = 0; textureCoords[currentIndex *2 +1] = bottomRadius / (bottomRadius + height + topRadius);
normals[currentIndex*3+2] = -1;
currentIndex++;
textureCoords[currentIndex *2] = (float) circlePoint / radialSamples; }
textureCoords[currentIndex *2 +1] = bottomRadius / (bottomRadius + height + topRadius); // Top
for (int circlePoint = 0; circlePoint < radialSamples + 1; circlePoint++) {
currentIndex++; vertices[currentIndex*3] = circlePoints[circlePoint][0] * topRadius;
} vertices[currentIndex*3 +1] = circlePoints[circlePoint][1] * topRadius;
// Top vertices[currentIndex*3 +2] = height/2;
for (int circlePoint = 0; circlePoint < radialSamples + 1; circlePoint++)
{ normals[currentIndex*3] = 0;
vertices[currentIndex*3] = circlePoints[circlePoint][0] * topRadius; normals[currentIndex*3+1] = 0;
vertices[currentIndex*3 +1] = circlePoints[circlePoint][1] * topRadius; normals[currentIndex*3+2] = 1;
vertices[currentIndex*3 +2] = height/2;
textureCoords[currentIndex *2] = (float) circlePoint / radialSamples;
normals[currentIndex*3] = 0; textureCoords[currentIndex *2 +1] = (bottomRadius + height) / (bottomRadius + height + topRadius);
normals[currentIndex*3+1] = 0;
normals[currentIndex*3+2] = 1; currentIndex++;
}
textureCoords[currentIndex *2] = (float) circlePoint / radialSamples;
textureCoords[currentIndex *2 +1] = (bottomRadius + height) / (bottomRadius + height + topRadius); // Add the centers of the caps.
vertices[currentIndex*3] = 0;
currentIndex++; vertices[currentIndex*3 +1] = 0;
} vertices[currentIndex*3 +2] = -height/2;
// Add the centers of the caps. normals[currentIndex*3] = 0;
vertices[currentIndex*3] = 0; normals[currentIndex*3+1] = 0;
vertices[currentIndex*3 +1] = 0; normals[currentIndex*3+2] = -1;
vertices[currentIndex*3 +2] = -height/2;
textureCoords[currentIndex *2] = 0.5f;
normals[currentIndex*3] = 0; textureCoords[currentIndex *2+1] = 0f;
normals[currentIndex*3+1] = 0;
normals[currentIndex*3+2] = -1; currentIndex++;
textureCoords[currentIndex *2] = 0.5f; vertices[currentIndex*3] = 0;
textureCoords[currentIndex *2+1] = 0f; vertices[currentIndex*3 +1] = 0;
vertices[currentIndex*3 +2] = height/2;
currentIndex++;
normals[currentIndex*3] = 0;
vertices[currentIndex*3] = 0; normals[currentIndex*3+1] = 0;
vertices[currentIndex*3 +1] = 0; normals[currentIndex*3+2] = 1;
vertices[currentIndex*3 +2] = height/2;
textureCoords[currentIndex *2] = 0.5f;
normals[currentIndex*3] = 0; textureCoords[currentIndex *2+1] = 1f;
normals[currentIndex*3+1] = 0;
normals[currentIndex*3+2] = 1;
textureCoords[currentIndex *2] = 0.5f;
textureCoords[currentIndex *2+1] = 1f;
} }
// Add the triangles indexes. // Add the triangles indexes.
short[] indices = new short[trianglesCount * 3]; short[] indices = new short[trianglesCount * 3];
currentIndex = 0; currentIndex = 0;
for (short axisSample = 0; axisSample < axisSamples - 1; axisSample++) for (short axisSample = 0; axisSample < axisSamples - 1; axisSample++) {
{ for (int circlePoint = 0; circlePoint < radialSamples; circlePoint++) {
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 * (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); indices[currentIndex++] = (short) ((axisSample + 1) * (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 + 1);
indices[currentIndex++] = (short) (axisSample * (radialSamples + 1) + circlePoint + 1); }
indices[currentIndex++] = (short) ((axisSample + 1) * (radialSamples + 1) + circlePoint + 1); }
} // Add caps if needed.
} if(closed) {
// Add caps if needed. short bottomCapIndex = (short) (verticesCount - 2);
if(closed) short topCapIndex = (short) (verticesCount - 1);
{
short bottomCapIndex = (short) (verticesCount - 2); int bottomRowOffset = (axisSamples) * (radialSamples +1 );
short topCapIndex = (short) (verticesCount - 1); int topRowOffset = (axisSamples+1) * (radialSamples +1 );
int bottomRowOffset = (axisSamples) * (radialSamples +1 ); for (int circlePoint = 0; circlePoint < radialSamples; circlePoint++) {
int topRowOffset = (axisSamples+1) * (radialSamples +1 ); indices[currentIndex++] = (short) (bottomRowOffset + circlePoint +1);
indices[currentIndex++] = (short) (bottomRowOffset + circlePoint);
for (int circlePoint = 0; circlePoint < radialSamples; circlePoint++) indices[currentIndex++] = bottomCapIndex;
{
indices[currentIndex++] = (short) (bottomRowOffset + circlePoint +1);
indices[currentIndex++] = (short) (bottomRowOffset + circlePoint); indices[currentIndex++] = (short) (topRowOffset + circlePoint);
indices[currentIndex++] = bottomCapIndex; indices[currentIndex++] = (short) (topRowOffset + circlePoint +1);
indices[currentIndex++] = topCapIndex;
}
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];
// If inverted, the triangles and normals are all reverted. indices[i] = indices[indices.length - 1 - i];
if (inverted) indices[indices.length - 1 - i] = temp;
{ }
for (int i = 0; i < indices.length / 2; i++)
{ for(int i = 0; i< normals.length; i++) {
short temp = indices[i]; normals[i] = -normals[i];
indices[i] = indices[indices.length - 1 - i]; }
indices[indices.length - 1 - i] = temp; }
}
// Fill in the buffers.
for(int i = 0; i< normals.length; i++) setBuffer(Type.Position, 3, BufferUtils.createFloatBuffer(vertices));
{ setBuffer(Type.Normal, 3, BufferUtils.createFloatBuffer(normals));
normals[i] = -normals[i]; setBuffer(Type.TexCoord, 2, BufferUtils.createFloatBuffer(textureCoords));
}
}
// 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)); setBuffer(Type.Index, 3, BufferUtils.createShortBuffer(indices));
updateBound(); updateBound();

@ -0,0 +1,78 @@
/*
* Copyright (c) 2009-2017 jMonkeyEngine
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.jme3.scene;
import com.jme3.collision.CollisionResults;
import com.jme3.math.FastMath;
import com.jme3.math.Ray;
import com.jme3.math.Vector3f;
import com.jme3.scene.shape.Cylinder;
import java.util.Random;
import org.junit.Test;
/**
* Ensures that geometries behave correctly, by casting rays and ensure they don't break.
*
* @author Christophe Carpentier
*/
public class ShapeGeometryTest {
protected static final int NUMBER_OF_TRIES = 1000;
@Test
public void testCylinders() {
Random random = new Random();
// Create a cylinder, cast a random ray, and ensure everything goes well.
Node scene = new Node("Scene Node");
for (int i = 0; i < NUMBER_OF_TRIES; i++) {
scene.detachAllChildren();
Cylinder cylinder = new Cylinder(2, 8, 1, 1, true);
Geometry geometry = new Geometry("cylinder", cylinder);
geometry.rotate(FastMath.HALF_PI, 0, 0);
scene.attachChild(geometry);
// Cast a random ray, and count successes and IndexOutOfBoundsExceptions.
Vector3f randomPoint = new Vector3f(random.nextFloat(), random.nextFloat(), random.nextFloat());
Vector3f randomDirection = new Vector3f(random.nextFloat(), random.nextFloat(), random.nextFloat());
randomDirection.normalizeLocal();
Ray ray = new Ray(randomPoint, randomDirection);
CollisionResults collisionResults = new CollisionResults();
// If the geometry is invalid, this should throw various exceptions.
scene.collideWith(ray, collisionResults);
}
}
}
Loading…
Cancel
Save