Home | History | Annotate | Download | only in shape
      1 /*
      2  * Copyright (c) 2009-2010 jMonkeyEngine
      3  * All rights reserved.
      4  *
      5  * Redistribution and use in source and binary forms, with or without
      6  * modification, are permitted provided that the following conditions are
      7  * met:
      8  *
      9  * * Redistributions of source code must retain the above copyright
     10  *   notice, this list of conditions and the following disclaimer.
     11  *
     12  * * Redistributions in binary form must reproduce the above copyright
     13  *   notice, this list of conditions and the following disclaimer in the
     14  *   documentation and/or other materials provided with the distribution.
     15  *
     16  * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
     17  *   may be used to endorse or promote products derived from this software
     18  *   without specific prior written permission.
     19  *
     20  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
     21  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
     22  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
     23  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
     24  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
     25  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
     26  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
     27  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
     28  * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
     29  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
     30  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     31  */
     32 
     33 // $Id: Cylinder.java 4131 2009-03-19 20:15:28Z blaine.dev $
     34 package com.jme3.scene.shape;
     35 
     36 import com.jme3.export.InputCapsule;
     37 import com.jme3.export.JmeExporter;
     38 import com.jme3.export.JmeImporter;
     39 import com.jme3.export.OutputCapsule;
     40 import com.jme3.math.FastMath;
     41 import com.jme3.math.Vector3f;
     42 import com.jme3.scene.Mesh;
     43 import com.jme3.scene.VertexBuffer.Type;
     44 import com.jme3.scene.mesh.IndexBuffer;
     45 import com.jme3.util.BufferUtils;
     46 import static com.jme3.util.BufferUtils.*;
     47 import java.io.IOException;
     48 import java.nio.FloatBuffer;
     49 
     50 /**
     51  * A simple cylinder, defined by it's height and radius.
     52  * (Ported to jME3)
     53  *
     54  * @author Mark Powell
     55  * @version $Revision: 4131 $, $Date: 2009-03-19 16:15:28 -0400 (Thu, 19 Mar 2009) $
     56  */
     57 public class Cylinder extends Mesh {
     58 
     59     private int axisSamples;
     60 
     61     private int radialSamples;
     62 
     63     private float radius;
     64     private float radius2;
     65 
     66     private float height;
     67     private boolean closed;
     68     private boolean inverted;
     69 
     70     /**
     71      * Default constructor for serialization only. Do not use.
     72      */
     73     public Cylinder() {
     74     }
     75 
     76     /**
     77      * Creates a new Cylinder. By default its center is the origin. Usually, a
     78      * higher sample number creates a better looking cylinder, but at the cost
     79      * of more vertex information.
     80      *
     81      * @param axisSamples
     82      *            Number of triangle samples along the axis.
     83      * @param radialSamples
     84      *            Number of triangle samples along the radial.
     85      * @param radius
     86      *            The radius of the cylinder.
     87      * @param height
     88      *            The cylinder's height.
     89      */
     90     public Cylinder(int axisSamples, int radialSamples,
     91             float radius, float height) {
     92         this(axisSamples, radialSamples, radius, height, false);
     93     }
     94 
     95     /**
     96      * Creates a new Cylinder. By default its center is the origin. Usually, a
     97      * higher sample number creates a better looking cylinder, but at the cost
     98      * of more vertex information. <br>
     99      * If the cylinder is closed the texture is split into axisSamples parts:
    100      * top most and bottom most part is used for top and bottom of the cylinder,
    101      * rest of the texture for the cylinder wall. The middle of the top is
    102      * mapped to texture coordinates (0.5, 1), bottom to (0.5, 0). Thus you need
    103      * a suited distorted texture.
    104      *
    105      * @param axisSamples
    106      *            Number of triangle samples along the axis.
    107      * @param radialSamples
    108      *            Number of triangle samples along the radial.
    109      * @param radius
    110      *            The radius of the cylinder.
    111      * @param height
    112      *            The cylinder's height.
    113      * @param closed
    114      *            true to create a cylinder with top and bottom surface
    115      */
    116     public Cylinder(int axisSamples, int radialSamples,
    117             float radius, float height, boolean closed) {
    118         this(axisSamples, radialSamples, radius, height, closed, false);
    119     }
    120 
    121     /**
    122      * Creates a new Cylinder. By default its center is the origin. Usually, a
    123      * higher sample number creates a better looking cylinder, but at the cost
    124      * of more vertex information. <br>
    125      * If the cylinder is closed the texture is split into axisSamples parts:
    126      * top most and bottom most part is used for top and bottom of the cylinder,
    127      * rest of the texture for the cylinder wall. The middle of the top is
    128      * mapped to texture coordinates (0.5, 1), bottom to (0.5, 0). Thus you need
    129      * a suited distorted texture.
    130      *
    131      * @param axisSamples
    132      *            Number of triangle samples along the axis.
    133      * @param radialSamples
    134      *            Number of triangle samples along the radial.
    135      * @param radius
    136      *            The radius of the cylinder.
    137      * @param height
    138      *            The cylinder's height.
    139      * @param closed
    140      *            true to create a cylinder with top and bottom surface
    141      * @param inverted
    142      *            true to create a cylinder that is meant to be viewed from the
    143      *            interior.
    144      */
    145     public Cylinder(int axisSamples, int radialSamples,
    146             float radius, float height, boolean closed, boolean inverted) {
    147         this(axisSamples, radialSamples, radius, radius, height, closed, inverted);
    148     }
    149 
    150     public Cylinder(int axisSamples, int radialSamples,
    151             float radius, float radius2, float height, boolean closed, boolean inverted) {
    152         super();
    153         updateGeometry(axisSamples, radialSamples, radius, radius2, height, closed, inverted);
    154     }
    155 
    156     /**
    157      * @return the number of samples along the cylinder axis
    158      */
    159     public int getAxisSamples() {
    160         return axisSamples;
    161     }
    162 
    163     /**
    164      * @return Returns the height.
    165      */
    166     public float getHeight() {
    167         return height;
    168     }
    169 
    170     /**
    171      * @return number of samples around cylinder
    172      */
    173     public int getRadialSamples() {
    174         return radialSamples;
    175     }
    176 
    177     /**
    178      * @return Returns the radius.
    179      */
    180     public float getRadius() {
    181         return radius;
    182     }
    183 
    184     public float getRadius2() {
    185         return radius2;
    186     }
    187 
    188     /**
    189      * @return true if end caps are used.
    190      */
    191     public boolean isClosed() {
    192         return closed;
    193     }
    194 
    195     /**
    196      * @return true if normals and uvs are created for interior use
    197      */
    198     public boolean isInverted() {
    199         return inverted;
    200     }
    201 
    202     /**
    203      * Rebuilds the cylinder based on a new set of parameters.
    204      *
    205      * @param axisSamples the number of samples along the axis.
    206      * @param radialSamples the number of samples around the radial.
    207      * @param radius the radius of the bottom of the cylinder.
    208      * @param radius2 the radius of the top of the cylinder.
    209      * @param height the cylinder's height.
    210      * @param closed should the cylinder have top and bottom surfaces.
    211      * @param inverted is the cylinder is meant to be viewed from the inside.
    212      */
    213     public void updateGeometry(int axisSamples, int radialSamples,
    214             float radius, float radius2, float height, boolean closed, boolean inverted) {
    215         this.axisSamples = axisSamples + (closed ? 2 : 0);
    216         this.radialSamples = radialSamples;
    217         this.radius = radius;
    218         this.radius2 = radius2;
    219         this.height = height;
    220         this.closed = closed;
    221         this.inverted = inverted;
    222 
    223 //        VertexBuffer pvb = getBuffer(Type.Position);
    224 //        VertexBuffer nvb = getBuffer(Type.Normal);
    225 //        VertexBuffer tvb = getBuffer(Type.TexCoord);
    226 
    227         // Vertices
    228         int vertCount = axisSamples * (radialSamples + 1) + (closed ? 2 : 0);
    229 
    230         setBuffer(Type.Position, 3, createVector3Buffer(getFloatBuffer(Type.Position), vertCount));
    231 
    232         // Normals
    233         setBuffer(Type.Normal, 3, createVector3Buffer(getFloatBuffer(Type.Normal), vertCount));
    234 
    235         // Texture co-ordinates
    236         setBuffer(Type.TexCoord, 2, createVector2Buffer(vertCount));
    237 
    238         int triCount = ((closed ? 2 : 0) + 2 * (axisSamples - 1)) * radialSamples;
    239 
    240         setBuffer(Type.Index, 3, createShortBuffer(getShortBuffer(Type.Index), 3 * triCount));
    241 
    242         // generate geometry
    243         float inverseRadial = 1.0f / radialSamples;
    244         float inverseAxisLess = 1.0f / (closed ? axisSamples - 3 : axisSamples - 1);
    245         float inverseAxisLessTexture = 1.0f / (axisSamples - 1);
    246         float halfHeight = 0.5f * height;
    247 
    248         // Generate points on the unit circle to be used in computing the mesh
    249         // points on a cylinder slice.
    250         float[] sin = new float[radialSamples + 1];
    251         float[] cos = new float[radialSamples + 1];
    252 
    253         for (int radialCount = 0; radialCount < radialSamples; radialCount++) {
    254             float angle = FastMath.TWO_PI * inverseRadial * radialCount;
    255             cos[radialCount] = FastMath.cos(angle);
    256             sin[radialCount] = FastMath.sin(angle);
    257         }
    258         sin[radialSamples] = sin[0];
    259         cos[radialSamples] = cos[0];
    260 
    261         // calculate normals
    262         Vector3f[] vNormals = null;
    263         Vector3f vNormal = Vector3f.UNIT_Z;
    264 
    265         if ((height != 0.0f) && (radius != radius2)) {
    266             vNormals = new Vector3f[radialSamples];
    267             Vector3f vHeight = Vector3f.UNIT_Z.mult(height);
    268             Vector3f vRadial = new Vector3f();
    269 
    270             for (int radialCount = 0; radialCount < radialSamples; radialCount++) {
    271                 vRadial.set(cos[radialCount], sin[radialCount], 0.0f);
    272                 Vector3f vRadius = vRadial.mult(radius);
    273                 Vector3f vRadius2 = vRadial.mult(radius2);
    274                 Vector3f vMantle = vHeight.subtract(vRadius2.subtract(vRadius));
    275                 Vector3f vTangent = vRadial.cross(Vector3f.UNIT_Z);
    276                 vNormals[radialCount] = vMantle.cross(vTangent).normalize();
    277             }
    278         }
    279 
    280         FloatBuffer nb = getFloatBuffer(Type.Normal);
    281         FloatBuffer pb = getFloatBuffer(Type.Position);
    282         FloatBuffer tb = getFloatBuffer(Type.TexCoord);
    283 
    284         // generate the cylinder itself
    285         Vector3f tempNormal = new Vector3f();
    286         for (int axisCount = 0, i = 0; axisCount < axisSamples; axisCount++, i++) {
    287             float axisFraction;
    288             float axisFractionTexture;
    289             int topBottom = 0;
    290             if (!closed) {
    291                 axisFraction = axisCount * inverseAxisLess; // in [0,1]
    292                 axisFractionTexture = axisFraction;
    293             } else {
    294                 if (axisCount == 0) {
    295                     topBottom = -1; // bottom
    296                     axisFraction = 0;
    297                     axisFractionTexture = inverseAxisLessTexture;
    298                 } else if (axisCount == axisSamples - 1) {
    299                     topBottom = 1; // top
    300                     axisFraction = 1;
    301                     axisFractionTexture = 1 - inverseAxisLessTexture;
    302                 } else {
    303                     axisFraction = (axisCount - 1) * inverseAxisLess;
    304                     axisFractionTexture = axisCount * inverseAxisLessTexture;
    305                 }
    306             }
    307 
    308             // compute center of slice
    309             float z = -halfHeight + height * axisFraction;
    310             Vector3f sliceCenter = new Vector3f(0, 0, z);
    311 
    312             // compute slice vertices with duplication at end point
    313             int save = i;
    314             for (int radialCount = 0; radialCount < radialSamples; radialCount++, i++) {
    315                 float radialFraction = radialCount * inverseRadial; // in [0,1)
    316                 tempNormal.set(cos[radialCount], sin[radialCount], 0.0f);
    317 
    318                 if (vNormals != null) {
    319                     vNormal = vNormals[radialCount];
    320                 } else if (radius == radius2) {
    321                     vNormal = tempNormal;
    322                 }
    323 
    324                 if (topBottom == 0) {
    325                     if (!inverted)
    326                         nb.put(vNormal.x).put(vNormal.y).put(vNormal.z);
    327                     else
    328                         nb.put(-vNormal.x).put(-vNormal.y).put(-vNormal.z);
    329                 } else {
    330                     nb.put(0).put(0).put(topBottom * (inverted ? -1 : 1));
    331                 }
    332 
    333                 tempNormal.multLocal((radius - radius2) * axisFraction + radius2)
    334                         .addLocal(sliceCenter);
    335                 pb.put(tempNormal.x).put(tempNormal.y).put(tempNormal.z);
    336 
    337                 tb.put((inverted ? 1 - radialFraction : radialFraction))
    338                         .put(axisFractionTexture);
    339             }
    340 
    341             BufferUtils.copyInternalVector3(pb, save, i);
    342             BufferUtils.copyInternalVector3(nb, save, i);
    343 
    344             tb.put((inverted ? 0.0f : 1.0f))
    345                     .put(axisFractionTexture);
    346         }
    347 
    348         if (closed) {
    349             pb.put(0).put(0).put(-halfHeight); // bottom center
    350             nb.put(0).put(0).put(-1 * (inverted ? -1 : 1));
    351             tb.put(0.5f).put(0);
    352             pb.put(0).put(0).put(halfHeight); // top center
    353             nb.put(0).put(0).put(1 * (inverted ? -1 : 1));
    354             tb.put(0.5f).put(1);
    355         }
    356 
    357         IndexBuffer ib = getIndexBuffer();
    358         int index = 0;
    359         // Connectivity
    360         for (int axisCount = 0, axisStart = 0; axisCount < axisSamples - 1; axisCount++) {
    361             int i0 = axisStart;
    362             int i1 = i0 + 1;
    363             axisStart += radialSamples + 1;
    364             int i2 = axisStart;
    365             int i3 = i2 + 1;
    366             for (int i = 0; i < radialSamples; i++) {
    367                 if (closed && axisCount == 0) {
    368                     if (!inverted) {
    369                         ib.put(index++, i0++);
    370                         ib.put(index++, vertCount - 2);
    371                         ib.put(index++, i1++);
    372                     } else {
    373                         ib.put(index++, i0++);
    374                         ib.put(index++, i1++);
    375                         ib.put(index++, vertCount - 2);
    376                     }
    377                 } else if (closed && axisCount == axisSamples - 2) {
    378                     ib.put(index++, i2++);
    379                     ib.put(index++, inverted ? vertCount - 1 : i3++);
    380                     ib.put(index++, inverted ? i3++ : vertCount - 1);
    381                 } else {
    382                     ib.put(index++, i0++);
    383                     ib.put(index++, inverted ? i2 : i1);
    384                     ib.put(index++, inverted ? i1 : i2);
    385                     ib.put(index++, i1++);
    386                     ib.put(index++, inverted ? i2++ : i3++);
    387                     ib.put(index++, inverted ? i3++ : i2++);
    388                 }
    389             }
    390         }
    391 
    392         updateBound();
    393     }
    394 
    395     public void read(JmeImporter e) throws IOException {
    396         super.read(e);
    397         InputCapsule capsule = e.getCapsule(this);
    398         axisSamples = capsule.readInt("axisSamples", 0);
    399         radialSamples = capsule.readInt("radialSamples", 0);
    400         radius = capsule.readFloat("radius", 0);
    401         radius2 = capsule.readFloat("radius2", 0);
    402         height = capsule.readFloat("height", 0);
    403         closed = capsule.readBoolean("closed", false);
    404         inverted = capsule.readBoolean("inverted", false);
    405     }
    406 
    407     public void write(JmeExporter e) throws IOException {
    408         super.write(e);
    409         OutputCapsule capsule = e.getCapsule(this);
    410         capsule.write(axisSamples, "axisSamples", 0);
    411         capsule.write(radialSamples, "radialSamples", 0);
    412         capsule.write(radius, "radius", 0);
    413         capsule.write(radius2, "radius2", 0);
    414         capsule.write(height, "height", 0);
    415         capsule.write(closed, "closed", false);
    416         capsule.write(inverted, "inverted", false);
    417     }
    418 
    419 
    420 }
    421