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      */
Cylinder()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      */
Cylinder(int axisSamples, int radialSamples, float radius, float height)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      */
Cylinder(int axisSamples, int radialSamples, float radius, float height, boolean closed)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      */
Cylinder(int axisSamples, int radialSamples, float radius, float height, boolean closed, boolean inverted)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 
Cylinder(int axisSamples, int radialSamples, float radius, float radius2, float height, boolean closed, boolean inverted)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      */
getAxisSamples()159     public int getAxisSamples() {
160         return axisSamples;
161     }
162 
163     /**
164      * @return Returns the height.
165      */
getHeight()166     public float getHeight() {
167         return height;
168     }
169 
170     /**
171      * @return number of samples around cylinder
172      */
getRadialSamples()173     public int getRadialSamples() {
174         return radialSamples;
175     }
176 
177     /**
178      * @return Returns the radius.
179      */
getRadius()180     public float getRadius() {
181         return radius;
182     }
183 
getRadius2()184     public float getRadius2() {
185         return radius2;
186     }
187 
188     /**
189      * @return true if end caps are used.
190      */
isClosed()191     public boolean isClosed() {
192         return closed;
193     }
194 
195     /**
196      * @return true if normals and uvs are created for interior use
197      */
isInverted()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      */
updateGeometry(int axisSamples, int radialSamples, float radius, float radius2, float height, boolean closed, boolean inverted)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 
read(JmeImporter e)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 
write(JmeExporter e)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