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