Trying to create sphere segment geometry, need help

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?)

1 Like

Just out of curiosity is there a reason you don’t just make a model of the wedge’s you want?

I need to create the segments dynamically. Believe me, a model will not solve my problem.