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