I need a new sphere segment geometry.
Goal: create segments like wedges or slices or a sphere.
It should look like this, but with the texture correctly fit to the segment:
I started to modify the sphere code but I don’t really understand the coordinate stuff.
Issues
The segments still do not fit horizontally together (but vertically they do):
As mentioned above, the texture coordinates are not scaled (but they should be).
This is how different segments should look like. The normals are still messed up, so I disabled face culling:
The lower and upper poles are also problematic.
Code
SphereSegment.java:
[java]import java.io.IOException;
import java.nio.FloatBuffer;
import java.nio.ShortBuffer;
import com.jme3.export.InputCapsule;
import com.jme3.export.JmeExporter;
import com.jme3.export.JmeImporter;
import com.jme3.export.OutputCapsule;
import com.jme3.math.FastMath;
import com.jme3.math.Vector3f;
import com.jme3.scene.Mesh;
import com.jme3.scene.VertexBuffer.Type;
import com.jme3.scene.shape.Sphere;
import com.jme3.scene.shape.Sphere.TextureMode;
import com.jme3.util.BufferUtils;
import com.jme3.util.TempVars;
/**
- A sphere segment is a wedge, cap or tile of a {@link Sphere}.
- TODO this code is broken, do not use it unless you are able to fix it!!
- @author Stephan Dreyer
- @author Joshua Slack (Original sphere code that was adapted)
- @version newest 2013-02-13 10:37:00, $Revision: 4163 $, $Date: 2009-03-24
-
21:14:55 -0400 (Tue, 24 Mar 2009) $
*/
public class SphereSegment extends Mesh {
protected int vertCount;
protected int triCount;
protected int zSamples;
protected int radialSamples;
protected boolean useEvenSlices;
protected boolean interior;
/** the distance from the center point each point falls on */
public float radius;
protected TextureMode textureMode = TextureMode.Original;
// the radial limits
private int minRad = 0;
private int maxRad = 8;
// the z direction limits
private int minZ = 0;
private int maxZ = 4;
/**
- Serialization only. Do not use.
*/
public SphereSegment() {
}
/**
- Constructs a sphere segment. All geometry data buffers are updated
- automatically. Both zSamples and radialSamples increase the quality of the
- generated sphere.
- @param zSamples
-
The number of samples along the Z.
- @param minZ
-
The lower limit of the segment between 0 and maxZ
- @param maxZ
-
The upper limit of the segment between minZ and 1
- @param radialSamples
-
The number of samples along the radial.
- @param minRad
-
The lower radial limit of the wedge between 0 and maxRad
- @param maxRad
-
The upper radial limit of the wedge between minRad and 1
- @param radius
-
The radius of the sphere.
*/
public SphereSegment(final int zSamples, final float minZ, final float maxZ,
final int radialSamples, final float minRad, final float maxRad,
final float radius) {
this(zSamples, minZ, maxZ, radialSamples, minRad, maxRad, radius, false,
false);
}
/**
- Constructs a sphere. Additional arg to evenly space latitudinal slices
- @param zSamples
-
The number of samples along the Z.
- @param minZ
-
The lower limit of the segment between 0 and maxZ
- @param maxZ
-
The upper limit of the segment between minZ and 1
- @param radialSamples
-
The number of samples along the radial.
- @param minRad
-
The lower radial limit of the wedge between 0 and maxRad
- @param maxRad
-
The upper radial limit of the wedge between minRad and 1
- @param radius
-
The radius of the sphere.
- @param useEvenSlices
-
Slice sphere evenly along the Z axis
- @param interior
-
Not yet documented
*/
public SphereSegment(final int zSamples, final float minZ, final float maxZ,
final int radialSamples, final float minRad, final float maxRad,
final float radius, final boolean useEvenSlices, final boolean interior) {
updateGeometry(zSamples, minZ, maxZ, radialSamples, minRad, maxRad, radius,
useEvenSlices, interior);
}
public int getRadialSamples() {
return radialSamples;
}
public float getRadius() {
return radius;
}
/**
-
@return Returns the textureMode.
*/
public TextureMode getTextureMode() {
return textureMode;
}
public int getZSamples() {
return zSamples;
}
/**
- builds the vertices based on the radius, radial and zSamples.
*/
private void setGeometryData() {
// allocate vertices
vertCount = (zSamples - minZ - 2) * (radialSamples - minRad + 1) + 2;
final FloatBuffer posBuf = BufferUtils.createVector3Buffer(vertCount);
// allocate normals if requested
final FloatBuffer normBuf = BufferUtils.createVector3Buffer(vertCount);
// allocate texture coordinates
final FloatBuffer texBuf = BufferUtils.createVector2Buffer(vertCount);
setBuffer(Type.Position, 3, posBuf);
setBuffer(Type.Normal, 3, normBuf);
setBuffer(Type.TexCoord, 2, texBuf);
// generate geometry
final float fInvRS = 1.0f / (radialSamples);
final float fZFactor = 2.0f / (zSamples - 1);
// Generate points on the unit circle to be used in computing the mesh
// points on a sphere slice.
final float[] afSin = new float[(maxRad - minRad + 1)];
final float[] afCos = new float[(maxRad - minRad + 1)];
for (int iR = minRad; iR < maxRad; iR++) {
final float fAngle = FastMath.TWO_PI * fInvRS * (iR);
afCos[iR - minRad] = FastMath.cos(fAngle);
afSin[iR - minRad] = FastMath.sin(fAngle);
}
afSin[maxRad - minRad] = afSin[0];
afCos[maxRad - minRad] = afCos[0];
final TempVars vars = TempVars.get();
final Vector3f tempVa = vars.vect1;
final Vector3f tempVb = vars.vect2;
final Vector3f tempVc = vars.vect3;
// generate the sphere itself
int i = 0;
for (int iZ = 1; iZ < (zSamples - minZ - 1); iZ++) {
final float fAFraction = FastMath.HALF_PI
* (-1.0f + fZFactor * (iZ + minZ + maxZ - 1)); // in
// (-pi/2,
// pi/2)
float fZFraction;
if (useEvenSlices) {
fZFraction = -1.0f + fZFactor * (iZ + minZ); // in (-1, 1)
} else {
fZFraction = FastMath.sin(fAFraction); // in (-1,1)
}
final float fZ = radius * fZFraction;
// compute center of slice
final Vector3f kSliceCenter = tempVb.set(Vector3f.ZERO);
kSliceCenter.z += fZ;
// compute radius of slice
final float fSliceRadius = FastMath.sqrt(FastMath.abs(radius * radius
- fZ * fZ));
// compute slice vertices with duplication at end point
Vector3f kNormal;
final int iSave = i;
for (int iR = minRad; iR < maxRad; iR++) {
final float fRadialFraction = iR * fInvRS; // in [0,1)
final Vector3f kRadial = tempVc.set(afCos[iR - minRad], afSin[iR
- minRad], 0);
kRadial.mult(fSliceRadius, tempVa);
posBuf.put(kSliceCenter.x + tempVa.x).put(kSliceCenter.y + tempVa.y)
.put(kSliceCenter.z + tempVa.z);
BufferUtils.populateFromBuffer(tempVa, posBuf, i);
kNormal = tempVa;
kNormal.normalizeLocal();
if (!interior) // allow interior texture vs. exterior
{
normBuf.put(kNormal.x).put(kNormal.y).put(kNormal.z);
} else {
normBuf.put(-kNormal.x).put(-kNormal.y).put(-kNormal.z);
}
if (textureMode == TextureMode.Original) {
texBuf.put(fRadialFraction).put(0.5f * (fZFraction + 1.0f));
} else if (textureMode == TextureMode.Projected) {
texBuf.put(fRadialFraction).put(
FastMath.INV_PI * (FastMath.HALF_PI + FastMath.asin(fZFraction)));
} else if (textureMode == TextureMode.Polar) {
final float r = (FastMath.HALF_PI - FastMath.abs(fAFraction))
/ FastMath.PI;
final float u = r * afCos[iR - minRad] + 0.5f;
final float v = r * afSin[iR - minRad] + 0.5f;
texBuf.put(u).put(v);
}
i++;
}
BufferUtils.copyInternalVector3(posBuf, iSave, i);
BufferUtils.copyInternalVector3(normBuf, iSave, i);
if (textureMode == TextureMode.Original) {
texBuf.put(1.0f).put(0.5f * (fZFraction + 1.0f));
} else if (textureMode == TextureMode.Projected) {
texBuf.put(1.0f).put(
FastMath.INV_PI * (FastMath.HALF_PI + FastMath.asin(fZFraction)));
} else if (textureMode == TextureMode.Polar) {
final float r = (FastMath.HALF_PI - FastMath.abs(fAFraction))
/ FastMath.PI;
texBuf.put(r + 0.5f).put(0.5f);
}
i++;
}
vars.release();
// south pole
posBuf.position(i * 3);
posBuf.put(0f).put(0f).put(-radius);
normBuf.position(i * 3);
if (!interior) {
normBuf.put(0).put(0).put(-1); // allow for inner
} // texture orientation
// later.
else {
normBuf.put(0).put(0).put(1);
}
texBuf.position(i * 2);
if (textureMode == TextureMode.Polar) {
texBuf.put(0.5f).put(0.5f);
} else {
texBuf.put(0.5f).put(0.0f);
}
i++;
// north pole
posBuf.put(0).put(0).put(radius);
if (!interior) {
normBuf.put(0).put(0).put(1);
} else {
normBuf.put(0).put(0).put(-1);
}
if (textureMode == TextureMode.Polar) {
texBuf.put(0.5f).put(0.5f);
} else {
texBuf.put(0.5f).put(1.0f);
}
updateBound();
setStatic();
}
/**
- sets the indices for rendering the sphere.
*/
private void setIndexData() {
// allocate connectivity
triCount = 2 * (zSamples - minZ - 2) * (maxRad - minRad);
final ShortBuffer idxBuf = BufferUtils.createShortBuffer(3 * triCount);
setBuffer(Type.Index, 3, idxBuf);
// generate connectivity
for (int iZ = 0, iZStart = 0; iZ < (zSamples - minZ - 3); iZ++) {
int i0 = iZStart;
int i1 = i0 + 1;
iZStart += (maxRad - minRad + 1);
int i2 = iZStart;
int i3 = i2 + 1;
for (int i = 0; i < maxRad - minRad; i++) {
if (!interior) {
idxBuf.put((short) i0++);
idxBuf.put((short) i1);
idxBuf.put((short) i2);
idxBuf.put((short) i1++);
idxBuf.put((short) i3++);
idxBuf.put((short) i2++);
} else { // inside view
idxBuf.put((short) i0++);
idxBuf.put((short) i2);
idxBuf.put((short) i1);
idxBuf.put((short) i1++);
idxBuf.put((short) i2++);
idxBuf.put((short) i3++);
}
}
}
// TODO the poles are not working correctly
// south pole triangles
if (maxZ == zSamples) {
// only add the south pole if the sphere is not segmented at the lower cap
for (int i = 0; i < maxRad - minRad; i++) {
if (!interior) {
idxBuf.put((short) i);
idxBuf.put((short) (vertCount - 2));
idxBuf.put((short) (i + 1));
} else { // inside view
idxBuf.put((short) i);
idxBuf.put((short) (i + 1));
idxBuf.put((short) (vertCount - 2));
}
}
}
// north pole triangles
if (minZ == 0) {
// only add the south pole if the sphere is not segmented at the upper cap
final int iOffset = ((maxZ - minZ) - 3) * (maxRad - minRad + 1);
for (int i = 0; i < maxRad - minRad; i++) {
if (!interior) {
idxBuf.put((short) (i + iOffset));
idxBuf.put((short) (i + 1 + iOffset));
idxBuf.put((short) (vertCount - 1));
} else { // inside view
idxBuf.put((short) (i + iOffset));
idxBuf.put((short) (vertCount - 1));
idxBuf.put((short) (i + 1 + iOffset));
}
}
}
}
/**
- @param textureMode
-
The textureMode to set.
*/
public void setTextureMode(final TextureMode textureMode) {
this.textureMode = textureMode;
setGeometryData();
}
/**
- Changes the information of the sphere into the given values.
- @param zSamples
-
The number of samples along the Z.
- @param minZ
-
The lower limit of the segment between 0 and maxZ
- @param maxZ
-
The upper limit of the segment between minZ and 1
- @param radialSamples
-
The number of samples along the radial.
- @param minRad
-
The lower radial limit of the wedge between 0 and maxRad
- @param maxRad
-
The upper radial limit of the wedge between minRad and 1
- @param radius
-
The radius of the sphere.
*/
public void updateGeometry(final int zSamples, final float minZ,
final float maxZ, final int radialSamples, final float minRad,
final float maxRad, final float radius) {
updateGeometry(zSamples, minZ, maxZ, radialSamples, minRad, maxRad, radius,
false, false);
}
public void updateGeometry(final int zSamples, final float minZ,
final float maxZ, final int radialSamples, final float minRad,
final float maxRad, final float radius, final boolean useEvenSlices,
final boolean interior) {
this.zSamples = zSamples;
this.radialSamples = radialSamples;
this.radius = radius;
this.useEvenSlices = useEvenSlices;
this.interior = interior;
this.minZ = Math.round(Math.max(0f, minZ) * zSamples);
this.maxZ = Math.round(Math.max(minZ, Math.min(maxZ, 1f)) * zSamples);
this.minRad = Math.round(Math.max(0f, minRad) * radialSamples);
this.maxRad = Math.round(Math.max(maxRad, Math.min(maxRad, 1f))
* radialSamples);
setGeometryData();
setIndexData();
}
@Override
public void read(final JmeImporter e) throws IOException {
super.read(e);
final InputCapsule capsule = e.getCapsule(this);
zSamples = capsule.readInt("zSamples", 0);
radialSamples = capsule.readInt("radialSamples", 0);
radius = capsule.readFloat("radius", 0);
useEvenSlices = capsule.readBoolean("useEvenSlices", false);
textureMode = capsule.readEnum("textureMode", TextureMode.class,
TextureMode.Original);
interior = capsule.readBoolean("interior", false);
// TODO add segment limits
}
@Override
public void write(final JmeExporter e) throws IOException {
super.write(e);
final OutputCapsule capsule = e.getCapsule(this);
capsule.write(zSamples, "zSamples", 0);
capsule.write(radialSamples, "radialSamples", 0);
capsule.write(radius, "radius", 0);
capsule.write(useEvenSlices, "useEvenSlices", false);
capsule.write(textureMode, "textureMode", TextureMode.Original);
capsule.write(interior, "interior", false);
// TODO add segment limits
}
} [/java]
TestSphereSegment.java:
[java]import com.jme3.app.SimpleApplication;
import com.jme3.light.AmbientLight;
import com.jme3.light.DirectionalLight;
import com.jme3.material.Material;
import com.jme3.material.RenderState.FaceCullMode;
import com.jme3.math.ColorRGBA;
import com.jme3.math.FastMath;
import com.jme3.math.Vector3f;
import com.jme3.scene.Geometry;
import com.jme3.scene.shape.Sphere.TextureMode;
public class TestSphereSegment extends SimpleApplication {
@Override
public void simpleInitApp() {
final Material mat = new Material(assetManager,
"Common/MatDefs/Light/Lighting.j3md");
mat.setColor("Ambient", ColorRGBA.Gray);
mat.setColor("Specular", ColorRGBA.Gray);
mat.setColor("Diffuse", ColorRGBA.White);
mat.setFloat("Shininess", 1f);
mat.getAdditionalRenderState().setFaceCullMode(FaceCullMode.Off);
for (int i = 0; i <= 4; i++) {
for (int j = 4; j < 8; j++) {
try {
final SphereSegment sphereSegment = new SphereSegment(16, i / 8f,
j / 8f, 16, i / 8f, 0.75f, 2.5f);
sphereSegment.setTextureMode(TextureMode.Projected);
final Geometry geo = new Geometry("Globe", sphereSegment);
geo.getLocalRotation().fromAngleAxis(-FastMath.HALF_PI,
Vector3f.UNIT_X);
geo.setMaterial(mat);
geo.setLocalTranslation(-25f + (5f * j), -10f + (5f * i), 0);
rootNode.attachChild(geo);
} catch (final Exception ex) {
System.err.println(i +" " + j);
ex.printStackTrace();
}
}
}
for (int i = 0; i < 4; i++) {
for (int j = 4; j < 8; j++) {
try {
final SphereSegment sphereSegment = new SphereSegment(16, i / 8f,
j / 8f, 16, 0f, 1f, 2.5f);
sphereSegment.setTextureMode(TextureMode.Projected);
final Geometry geo = new Geometry("Globe", sphereSegment);
geo.getLocalRotation().fromAngleAxis(-FastMath.HALF_PI,
Vector3f.UNIT_X);
geo.setMaterial(mat);
geo.setLocalTranslation(5f + (5f * j), -10f + (5f * i), 0);
rootNode.attachChild(geo);
} catch (final Exception ex) {
System.err.println(i +" " + j);
ex.printStackTrace();
}
}
}
rootNode.addLight(new AmbientLight());
final DirectionalLight dl = new DirectionalLight();
dl.setDirection(new Vector3f(1f, -0.1f, 0.1f));
rootNode.addLight(dl);
flyCam.setMoveSpeed(12f);
}
public static void main(final String[] args) {
new TestSphereSegment().start();
}
}[/java]
Maybe someone is also interested in such a geometry and could help or has a working solution for this and wants to share. (Maybe @gasher, who created the dome class?)