/* * Copyright (c) 2009-2012 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. */ // $Id: Cylinder.java 4131 2009-03-19 20:15:28Z blaine.dev $ package com.jme3.scene.shape; import com.jme3.export.InputCapsule; import com.jme3.export.JmeExporter; import com.jme3.export.JmeImporter; import com.jme3.export.OutputCapsule; 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. * (Ported to jME3) * * @author Mark Powell * @version $Revision: 4131 $, $Date: 2009-03-19 16:15:28 -0400 (Thu, 19 Mar 2009) $ */ public class Cylinder extends Mesh { private int axisSamples; private int radialSamples; private float radius; private float radius2; private float height; private boolean closed; private boolean inverted; /** * Default constructor for serialization only. Do not use. */ public Cylinder() { } /** * Creates a new Cylinder. By default its center is the origin. Usually, a * higher sample number creates a better looking cylinder, but at the cost * of more vertex information. * * @param axisSamples * Number of triangle samples along the axis. * @param radialSamples * Number of triangle samples along the radial. * @param radius * The radius of the cylinder. * @param height * The cylinder's height. */ public Cylinder(int axisSamples, int radialSamples, float radius, float height) { this(axisSamples, radialSamples, radius, height, false); } /** * Creates a new Cylinder. By default its center is the origin. Usually, a * higher sample number creates a better looking cylinder, but at the cost * of more vertex information. <br> * If the cylinder is closed the texture is split into axisSamples parts: * top most and bottom most part is used for top and bottom of the cylinder, * rest of the texture for the cylinder wall. The middle of the top is * 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 radius * The radius of the cylinder. * @param height * The cylinder's height. * @param closed * true to create a cylinder with top and bottom surface */ public Cylinder(int axisSamples, int radialSamples, float radius, float height, boolean closed) { this(axisSamples, radialSamples, radius, height, closed, false); } /** * Creates a new Cylinder. By default its center is the origin. Usually, a * higher sample number creates a better looking cylinder, but at the cost * of more vertex information. <br> * If the cylinder is closed the texture is split into axisSamples parts: * top most and bottom most part is used for top and bottom of the cylinder, * rest of the texture for the cylinder wall. The middle of the top is * 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 radius * The radius of the cylinder. * @param height * The cylinder's height. * @param closed * true to create a cylinder with top and bottom surface * @param inverted * true to create a cylinder that is meant to be viewed from the * interior. */ public Cylinder(int axisSamples, int radialSamples, float radius, float height, boolean closed, boolean inverted) { this(axisSamples, radialSamples, radius, radius, height, closed, inverted); } public Cylinder(int axisSamples, int radialSamples, float radius, float radius2, float height, boolean closed, boolean inverted) { super(); updateGeometry(axisSamples, radialSamples, radius, radius2, height, closed, inverted); } /** * @return the number of samples along the cylinder axis */ public int getAxisSamples() { return axisSamples; } /** * @return Returns the height. */ public float getHeight() { return height; } /** * @return number of samples around cylinder */ public int getRadialSamples() { return radialSamples; } /** * @return Returns the radius. */ public float getRadius() { return radius; } public float getRadius2() { return radius2; } /** * @return true if end caps are used. */ public boolean isClosed() { return closed; } /** * @return true if normals and uvs are created for interior use */ public boolean isInverted() { return inverted; } /** * 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 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; this.radialSamples = radialSamples; this.radius = radius; this.radius2 = radius2; 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); } 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); } 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++); } } } updateBound(); setStatic(); } @Override public void read(JmeImporter e) throws IOException { super.read(e); InputCapsule capsule = e.getCapsule(this); axisSamples = capsule.readInt("axisSamples", 0); radialSamples = capsule.readInt("radialSamples", 0); radius = capsule.readFloat("radius", 0); radius2 = capsule.readFloat("radius2", 0); height = capsule.readFloat("height", 0); closed = capsule.readBoolean("closed", false); inverted = capsule.readBoolean("inverted", false); } @Override public void write(JmeExporter e) throws IOException { super.write(e); OutputCapsule capsule = e.getCapsule(this); capsule.write(axisSamples, "axisSamples", 0); capsule.write(radialSamples, "radialSamples", 0); capsule.write(radius, "radius", 0); capsule.write(radius2, "radius2", 0); capsule.write(height, "height", 0); capsule.write(closed, "closed", false); capsule.write(inverted, "inverted", false); } }