Overview
I wanted to add a quarter-tube and half-tube to some scene I was experimenting with but found that tube shapes can't be parametrized with angles. I modified the tube shape and also the disk shape to accept angle parameters and here's the result:
Changes
The default assumption was that the shape will wrap around on itself, so the first vertices were to be connected to the last ones. I had to add another round of vertices so the shape goes around up to the desired angle. This is necessary when the angle is less than 2xPi but if the angle is 2xPi, these newly added vertices will be the same as the first ones, which means they would be redundant in that case, but it makes the shape general for any angle. This applies to both tubes and disks.
Also, for the tube shape, I had to close the introduced edges as the tube gets cut along its axis. I connected the right vertices from the top and bottom surfaces without adding any more vertices. For now, these vertical edges have no texture coordinates since I didn't add more vertices for them.
Changed Code
Disk.java
import java.io.IOException;
import com.jme.math.FastMath;
import com.jme.math.Vector2f;
import com.jme.math.Vector3f;
import com.jme.scene.TexCoords;
import com.jme.scene.TriMesh;
import com.jme.util.export.InputCapsule;
import com.jme.util.export.JMEExporter;
import com.jme.util.export.JMEImporter;
import com.jme.util.export.OutputCapsule;
import com.jme.util.geom.BufferUtils;
/**
* A flat discus, defined by it's radius.
*
* @author Mark Powell
* @version $Revision: 4091 $, $Date: 2009-01-21 21:01:20 +0200 (Wed, 21 Jan 2009) $
*/
public class Disk extends TriMesh {
private static final long serialVersionUID = 1L;
private int shellSamples;
private int radialSamples;
private float radius;
private float arcAngle;
public Disk() {
}
/**
* Creates a flat disk (circle) at the origin flat along the Z. Usually, a
* higher sample number creates a better looking cylinder, but at the cost
* of more vertex information.
*
* @param name
* The name of the disk.
* @param shellSamples
* The number of shell samples.
* @param radialSamples
* The number of radial samples.
* @param radius
* The radius of the disk.
*/
public Disk(String name, int shellSamples, int radialSamples, float radius) {
super(name);
updateGeometry(shellSamples, radialSamples, radius, FastMath.TWO_PI);
}
public Disk(String name, int shellSamples, int radialSamples, float radius, float arcAngle) {
super(name);
updateGeometry(shellSamples, radialSamples, radius, arcAngle);
}
public float getArcAngle() {
return arcAngle;
}
public int getRadialSamples() {
return radialSamples;
}
public float getRadius() {
return radius;
}
public int getShellSamples() {
return shellSamples;
}
public void read(JMEImporter e) throws IOException {
super.read(e);
InputCapsule capsule = e.getCapsule(this);
shellSamples = capsule.readInt("shellSamples", 0);
radialSamples = capsule.readInt("radialSamples", 0);
radius = capsule.readFloat("raidus", 0);
arcAngle = capsule.readFloat("arcAngle", FastMath.TWO_PI);
}
/**
* Rebuild this disk based on a new set of parameters.
*
* @param shellSamples the number of shell samples.
* @param radialSamples the number of radial samples.
* @param radius the radius of the disk.
* @param arc angle the disk is to cover.
*/
public void updateGeometry(int shellSamples, int radialSamples, float radius, float arcAngle) {
this.shellSamples = shellSamples;
this.radialSamples = radialSamples;
this.radius = radius;
this.arcAngle = arcAngle;
int shellLess = shellSamples - 1;
// Allocate vertices
setVertexCount(1 + (radialSamples+1) * shellLess);
setVertexBuffer(BufferUtils.createVector3Buffer(getVertexCount()));
setNormalBuffer(BufferUtils.createVector3Buffer(getVertexCount()));
getTextureCoords().set(0, new TexCoords(BufferUtils.createVector3Buffer(getVertexCount())));
setTriangleQuantity(radialSamples * (2 * shellLess - 1));
setIndexBuffer(BufferUtils.createIntBuffer(3 * getTriangleCount()));
// generate geometry
// center of disk
getVertexBuffer().put(0).put(0).put(0);
for (int x = 0; x < getVertexCount(); x++) {
getNormalBuffer().put(0).put(0).put(1);
}
getTextureCoords().get(0).coords.put(.5f).put(.5f);
float inverseShellLess = 1.0f / shellLess;
float inverseRadial = 1.0f / radialSamples;
Vector3f radialFraction = new Vector3f();
Vector2f texCoord = new Vector2f();
for (int radialCount = 0; radialCount <= radialSamples; radialCount++) {
float angle = arcAngle * inverseRadial * radialCount;
float cos = FastMath.cos(angle);
float sin = FastMath.sin(angle);
Vector3f radial = new Vector3f(cos, sin, 0);
for (int shellCount = 1; shellCount < shellSamples; shellCount++) {
float fraction = inverseShellLess * shellCount; // in (0,R]
radialFraction.set(radial).multLocal(fraction);
int i = shellCount + shellLess * radialCount;
texCoord.x = 0.5f * (1.0f + radialFraction.x);
texCoord.y = 0.5f * (1.0f + radialFraction.y);
BufferUtils.setInBuffer(texCoord, getTextureCoords().get(0).coords, i);
radialFraction.multLocal(radius);
BufferUtils.setInBuffer(radialFraction, getVertexBuffer(), i);
}
}
// Generate connectivity
int index = 0;
for (int radialCount0 = 0, radialCount1 = 1; radialCount1 <= radialSamples; radialCount0 = radialCount1++) {
getIndexBuffer().put(0);
getIndexBuffer().put(1 + shellLess * radialCount0);
getIndexBuffer().put(1 + shellLess * radialCount1);
index += 3;
for (int iS = 1; iS < shellLess; iS++, index += 6) {
int i00 = iS + shellLess * radialCount0;
int i01 = iS + shellLess * radialCount1;
int i10 = i00 + 1;
int i11 = i01 + 1;
getIndexBuffer().put(i00);
getIndexBuffer().put(i10);
getIndexBuffer().put(i11);
getIndexBuffer().put(i00);
getIndexBuffer().put(i11);
getIndexBuffer().put(i01);
}
}
}
public void write(JMEExporter e) throws IOException {
super.write(e);
OutputCapsule capsule = e.getCapsule(this);
capsule.write(shellSamples, "shellSamples", 0);
capsule.write(radialSamples, "radialSamples", 0);
capsule.write(radius, "radius", 0);
capsule.write(arcAngle, "arcAngle", 0);
}
}
Tube.java
import java.io.IOException;
import com.jme.math.FastMath;
import com.jme.scene.TexCoords;
import com.jme.scene.TriMesh;
import com.jme.util.export.InputCapsule;
import com.jme.util.export.JMEExporter;
import com.jme.util.export.JMEImporter;
import com.jme.util.export.OutputCapsule;
import com.jme.util.export.Savable;
import com.jme.util.geom.BufferUtils;
/**
*
* @author Landei
*/
public class Tube extends TriMesh implements Savable {
private static final long serialVersionUID = 1L;
@Deprecated
public static long getSerialVersionUID() {
return serialVersionUID;
}
private int axisSamples;
private int radialSamples;
private float outerRadius;
private float innerRadius;
private float height;
private float arcAngle;
/**
* Constructor meant for Savable use only.
*/
public Tube() {
}
public Tube(String name, float outerRadius, float innerRadius,
float height) {
this(name, outerRadius, innerRadius, height, 2, 20, FastMath.TWO_PI);
}
public Tube(String name, float outerRadius, float innerRadius,
float height, float arcAngle) {
this(name, outerRadius, innerRadius, height, 2, 20 * (int) (FastMath
.ceil(arcAngle * FastMath.INV_TWO_PI)), arcAngle);
}
public Tube(String name, float outerRadius, float innerRadius,
float height, int axisSamples, int radialSamples, float arcAngle) {
super(name);
updateGeometry(outerRadius, innerRadius, height, axisSamples,
radialSamples, arcAngle);
}
public float getArcAngle() {
return arcAngle;
}
public int getAxisSamples() {
return axisSamples;
}
public float getHeight() {
return height;
}
public float getInnerRadius() {
return innerRadius;
}
public float getOuterRadius() {
return outerRadius;
}
public int getRadialSamples() {
return radialSamples;
}
@Override
public void read(JMEImporter e) throws IOException {
super.read(e);
InputCapsule capsule = e.getCapsule(this);
int axisSamples = capsule.readInt("axisSamples", 0);
int radialSamples = capsule.readInt("radialSamples", 0);
float outerRadius = capsule.readFloat("outerRadius", 0);
float innerRadius = capsule.readFloat("innerRadius", 0);
float height = capsule.readFloat("height", 0);
float angle = capsule.readFloat("angle", FastMath.TWO_PI);
updateGeometry(outerRadius, innerRadius, height, axisSamples,
radialSamples, angle);
}
private void setGeometryData() {
float inverseRadial = 1.0f / radialSamples;
float axisStep = height / axisSamples;
float axisTextureStep = 1.0f / axisSamples;
float halfHeight = 0.5f * height;
float innerOuterRatio = innerRadius / outerRadius;
float[] sin = new float[radialSamples + 1];
float[] cos = new float[radialSamples + 1];
for (int radialCount = 0; radialCount <= radialSamples; radialCount++) {
float angle = arcAngle * inverseRadial * radialCount;
cos[radialCount] = FastMath.cos(angle);
sin[radialCount] = FastMath.sin(angle);
}
// outer cylinder
for (int radialCount = 0; radialCount <= radialSamples; radialCount++) {
for (int axisCount = 0; axisCount <= axisSamples; axisCount++) {
getVertexBuffer().put(cos[radialCount] * outerRadius).put(
axisStep * axisCount - halfHeight).put(
sin[radialCount] * outerRadius);
getNormalBuffer().put(cos[radialCount]).put(0).put(
sin[radialCount]);
getTextureCoords(0).coords.put(radialCount * inverseRadial)
.put(axisTextureStep * axisCount);
}
}
// inner cylinder
for (int radialCount = 0; radialCount <= radialSamples; radialCount++) {
for (int axisCount = 0; axisCount <= axisSamples; axisCount++) {
getVertexBuffer().put(cos[radialCount] * innerRadius).put(
axisStep * axisCount - halfHeight).put(
sin[radialCount] * innerRadius);
getNormalBuffer().put(-cos[radialCount]).put(0).put(
-sin[radialCount]);
getTextureCoords(0).coords.put(radialCount * inverseRadial)
.put(axisTextureStep * axisCount);
}
}
// bottom edge
for (int radialCount = 0; radialCount <= radialSamples; radialCount++) {
getVertexBuffer().put(cos[radialCount] * outerRadius).put(
-halfHeight).put(sin[radialCount] * outerRadius);
getVertexBuffer().put(cos[radialCount] * innerRadius).put(
-halfHeight).put(sin[radialCount] * innerRadius);
getNormalBuffer().put(0).put(-1).put(0);
getNormalBuffer().put(0).put(-1).put(0);
getTextureCoords(0).coords.put(0.5f + 0.5f * cos[radialCount]).put(
0.5f + 0.5f * sin[radialCount]);
getTextureCoords(0).coords.put(
0.5f + innerOuterRatio * 0.5f * cos[radialCount]).put(
0.5f + innerOuterRatio * 0.5f * sin[radialCount]);
}
// top edge
for (int radialCount = 0; radialCount <= radialSamples; radialCount++) {
getVertexBuffer().put(cos[radialCount] * outerRadius).put(
halfHeight).put(sin[radialCount] * outerRadius);
getVertexBuffer().put(cos[radialCount] * innerRadius).put(
halfHeight).put(sin[radialCount] * innerRadius);
getNormalBuffer().put(0).put(1).put(0);
getNormalBuffer().put(0).put(1).put(0);
getTextureCoords(0).coords.put(0.5f + 0.5f * cos[radialCount]).put(
0.5f + 0.5f * sin[radialCount]);
getTextureCoords(0).coords.put(
0.5f + innerOuterRatio * 0.5f * cos[radialCount]).put(
0.5f + innerOuterRatio * 0.5f * sin[radialCount]);
}
}
private void setIndexData() {
int axisSamplesPlusOne = axisSamples + 1;
int innerCylinder = axisSamplesPlusOne * (radialSamples + 1);
int bottomEdge = 2 * innerCylinder;
int topEdge = bottomEdge + 2 * (radialSamples + 1);
// outer cylinder
for (int radialCount = 0; radialCount < radialSamples; radialCount++) {
for (int axisCount = 0; axisCount < axisSamples; axisCount++) {
int index0 = axisCount + axisSamplesPlusOne * radialCount;
int index1 = index0 + 1;
int index2 = index0 + axisSamplesPlusOne;
int index3 = index2 + 1;
getIndexBuffer().put(index0).put(index1).put(index2);
getIndexBuffer().put(index1).put(index3).put(index2);
}
}
// inner cylinder
for (int radialCount = 0; radialCount < radialSamples; radialCount++) {
for (int axisCount = 0; axisCount < axisSamples; axisCount++) {
int index0 = innerCylinder + axisCount + axisSamplesPlusOne
* radialCount;
int index1 = index0 + 1;
int index2 = index0 + axisSamplesPlusOne;
int index3 = index2 + 1;
getIndexBuffer().put(index0).put(index2).put(index1);
getIndexBuffer().put(index1).put(index2).put(index3);
}
}
// bottom edge
for (int radialCount = 0; radialCount < radialSamples; radialCount++) {
int index0 = bottomEdge + 2 * radialCount;
int index1 = index0 + 1;
int index2 = index1 + 1;
int index3 = index2 + 1;
getIndexBuffer().put(index0).put(index2).put(index1);
getIndexBuffer().put(index1).put(index2).put(index3);
}
// top edge
for (int radialCount = 0; radialCount < radialSamples; radialCount++) {
int index0 = topEdge + 2 * radialCount;
int index1 = index0 + 1;
int index2 = index1 + 1;
int index3 = index2 + 1;
getIndexBuffer().put(index0).put(index1).put(index2);
getIndexBuffer().put(index1).put(index3).put(index2);
}
// vertical edge0
int bottomIndex0 = 0;
int bottomIndex1 = innerCylinder;
int topIndex0 = axisSamples;
int topIndex1 = bottomIndex1 + axisSamples;
getIndexBuffer().put(bottomIndex0).put(bottomIndex1).put(topIndex0);
getIndexBuffer().put(bottomIndex1).put(topIndex1).put(topIndex0);
// vertical edge1
bottomIndex0 = innerCylinder - (axisSamples + 1);
bottomIndex1 = 2 * innerCylinder - (axisSamples + 1);
topIndex0 = bottomIndex0 + axisSamples;
topIndex1 = bottomIndex1 + axisSamples;
getIndexBuffer().put(bottomIndex0).put(bottomIndex1).put(topIndex0);
getIndexBuffer().put(bottomIndex1).put(topIndex1).put(topIndex0);
}
public void updateGeometry(float outerRadius, float innerRadius,
float height, int axisSamples, int radialSamples, float angle) {
this.outerRadius = outerRadius;
this.innerRadius = innerRadius;
this.height = height;
this.axisSamples = axisSamples;
this.radialSamples = radialSamples;
this.arcAngle = angle;
setVertexCount(2 * (axisSamples + 1) * (radialSamples + 1)
+ (radialSamples + 1) * 4);
setVertexBuffer(BufferUtils.createVector3Buffer(getVertexBuffer(),
getVertexCount()));
setNormalBuffer(BufferUtils.createVector3Buffer(getNormalBuffer(),
getVertexCount()));
getTextureCoords()
.set(
0,
new TexCoords(BufferUtils
.createVector2Buffer(getVertexCount())));
setTriangleQuantity(4 * (radialSamples + 1) * (axisSamples + 1));
setIndexBuffer(BufferUtils.createIntBuffer(getIndexBuffer(),
3 * getTriangleCount()));
setGeometryData();
setIndexData();
}
@Override
public void write(JMEExporter e) throws IOException {
super.write(e);
OutputCapsule capsule = e.getCapsule(this);
capsule.write(getAxisSamples(), "axisSamples", 0);
capsule.write(getRadialSamples(), "radialSamples", 0);
capsule.write(getOuterRadius(), "outerRadius", 0);
capsule.write(getInnerRadius(), "innerRadius", 0);
capsule.write(getHeight(), "height", 0);
}
}
Thoughts
I wasn't really concerned about textures. I also found that if we don't need to apply textures to the top and bottom surfaces, we can get rid of the extra vertices added specially for them and just connect vertices from the inner and outer cylinders. This would reduce the number of vertices by 4xRadialSamples, but the number of triangles will still be the same.
I'm still very new to graphics in general. I'd be happy to hear your comments and feedback.