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 $
34package com.jme3.scene.shape;
35
36import com.jme3.export.InputCapsule;
37import com.jme3.export.JmeExporter;
38import com.jme3.export.JmeImporter;
39import com.jme3.export.OutputCapsule;
40import com.jme3.math.FastMath;
41import com.jme3.math.Vector3f;
42import com.jme3.scene.Mesh;
43import com.jme3.scene.VertexBuffer.Type;
44import com.jme3.scene.mesh.IndexBuffer;
45import com.jme3.util.BufferUtils;
46import static com.jme3.util.BufferUtils.*;
47import java.io.IOException;
48import 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 */
57public 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