Hi,
for my project, I will need a quarter of a sphere (created from the Sphere shape class) and I will need dynamic extrusion of the “square” faces (ie not the ones using the north or south pole vertices).
I’ve took a look at the Extrusion class but this doesn’t seem to be what I’m looking for.
Since a picture help more than words, I would like to change this sphere: sphere
to extrude a “square” face (in fact two triangles) like that: sphere extruded
So:
1/ is it feasible? (I mean Opengl restrictions :?)
2/ is it feasible with JME?
3/ if yes, how?
TIA,
Niggle
You will need to go through the Vertex buffer and manipulate each of the vertices individually I think.
An easy test would be to see if the vertex is 'shared' more than 8 times (I think), if it is its a 'pole' vertex.
You might want to look at your own sphere building algorithm also, it might be less work…
Here's one I wrote a while ago (in GFX class), its for Jogl though so you will have to figure out how to implement the index buffer; and also it uses a QuadStrip which I don't know if jME supports…
private void renderBall( GL gl, double r, int lats, int longs ) {
gl.glColor4d( 1.0, 1.0, 1.0, 1.0 );
setBallMaterial( gl );
gl.glEnable( GL_NORMALIZE ); // We are scaling so request that openGL normalizes light normals
gl.glPushMatrix();
gl.glScaled( r, r, r );
gl.glBegin( GL_QUAD_STRIP );
for( int i = 0; i <= lats; ++i ){
double lat0 = Math.PI * ( -0.5 + (double) ( i - 1 ) / lats );
double z0 = Math.sin( lat0 );
double zr0 = Math.cos( lat0 );
double lat1 = Math.PI * ( -0.5 + (double) i / lats );
double z1 = Math.sin( lat1 );
double zr1 = Math.cos( lat1 );
for( int j = 0; j <= longs; ++j ){
double lng = 2 * Math.PI * (double) ( j - 1 ) / longs;
double x = Math.cos( lng );
double y = Math.sin( lng );
if( i > lats / 2 ){
gl.glNormal3d( x * zr1, y * zr1, z1 );
gl.glTexCoord2d( x * zr1 / 2 + 0.5, -y * zr1 / 2 + 0.5 );
gl.glVertex3d( x * zr1, y * zr1, z1 );
gl.glNormal3d( x * zr0, y * zr0, z0 );
gl.glTexCoord2d( x * zr0 / 2 + 0.5, -y * zr0 / 2 + 0.5 );
gl.glVertex3d( x * zr0, y * zr0, z0 );
// Reverse texture x on 'front' of ball, texture is backwards otherwise
} else{
gl.glNormal3d( x * zr1, y * zr1, z1 );
gl.glTexCoord2d( -x * zr1 / 2 + 0.5, -y * zr1 / 2 + 0.5 );
gl.glVertex3d( x * zr1, y * zr1, z1 );
gl.glNormal3d( x * zr0, y * zr0, z0 );
gl.glTexCoord2d( -x * zr0 / 2 + 0.5, -y * zr0 / 2 + 0.5 );
gl.glVertex3d( x * zr0, y * zr0, z0 );
}
}
}
gl.glEnd();
gl.glPopMatrix();
gl.glDisable( GL_NORMALIZE );
}
Thanks basixs,
now I can modify the vertex buffer and I can sort of morph some points of my sphere (inspired from com.jme.shape.Sphere) => less work (thanks though for your code).
But now I'm facing two issues:
-1- I added one more vertex for each vertices (but the 2 poles vertices) and I guess I'm now fighting with the index buffer (it is no more a Sphere :-o)
-2- I have to find the complex math stuff in order to extrude to create a Box and not some sort of 4 sided cones (because if I use the direction center -> vertex and if I multiply by a certain amount (> 1.0) for extrusion, I get an extruded face bigger than the original one in surface I mean)
any idea for the second point?
Yes first point resolved!
now, the second one… tomorrow!
QuarterSphere.java so far:
import com.jme.math.FastMath;
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;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
/**
* <code>Sphere</code> represents a 3D object with all points equidistance
* from a center point.
*
* @author Joshua Slack
* @author <a href="mailto:loic.lefevre@gmail.com">Lo
TODO:
- correct box-shaped extrusion direction
- connect 4 more new faces
Finally:
import com.jme.math.FastMath;
import com.jme.math.Vector3f;
import com.jme.renderer.ColorRGBA;
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;
import java.io.IOException;
import java.nio.FloatBuffer;
import java.util.ArrayList;
import java.util.List;
/**
* <code>Quarter of a sphere</code> used as the control panel for Oracle sessions
* Inspired from the Sphere shape.
*/
public class QuarterSphere extends TriMesh {
private static final long serialVersionUID = 1L;
protected int zSamples;
protected int radialSamples;
protected static final ColorRGBA SPHERE_COLOR = new ColorRGBA(1.0f, 1.0f, 1.0f, 1.0f);
/**
* the distance from the center point each point falls on
*/
public float radius;
/**
* the center of the sphere
*/
public Vector3f center;
protected transient FloatBuffer originalVerticesCoords;
protected transient FloatBuffer tempExtrudedVertices;
protected transient FloatBuffer tempExtrudedColors;
public QuarterSphere() {
}
/**
* Constructs a sphere. By default the Sphere has not geometry data or
* center.
*
* @param name The name of the sphere.
*/
public QuarterSphere(String name) {
super(name);
}
/**
* Constructs a sphere with center at the origin. For details, see the other
* constructor.
*
* @param name Name of sphere.
* @param zSamples The samples along the Z.
* @param radialSamples The samples along the radial.
* @param radius Radius of the sphere.
* @see #QuarterSphere(java.lang.String, com.jme.math.Vector3f, int, int, float)
*/
public QuarterSphere(String name, int zSamples, int radialSamples, float radius) {
this(name, new Vector3f(0, 0, 0), zSamples, radialSamples, radius);
}
/**
* Constructs a sphere. All geometry data buffers are updated automatically.
* Both zSamples and radialSamples increase the quality of the generated
* sphere.
*
* @param name Name of the sphere.
* @param center Center of the sphere.
* @param zSamples The number of samples along the Z.
* @param radialSamples The number of samples along the radial.
* @param radius The radius of the sphere.
*/
public QuarterSphere(String name, Vector3f center, int zSamples,
int radialSamples, float radius) {
super(name);
setData(center, zSamples, radialSamples, radius);
}
/**
* Changes the information of the sphere into the given values.
*
* @param center The new center of the sphere.
* @param zSamples The new number of zSamples of the sphere.
* @param radialSamples The new number of radial samples of the sphere.
* @param radius The new radius of the sphere.
*/
public void setData(Vector3f center, int zSamples, int radialSamples, float radius) {
if (center != null)
this.center = center;
else
this.center = new Vector3f(0, 0, 0);
this.zSamples = zSamples;
this.radialSamples = radialSamples;
this.radius = radius;
setGeometryData();
setIndexData();
}
/**
* builds the vertices based on the radius, center and radial and zSamples.
*/
private void setGeometryData() {
// allocate vertices
setVertexCount(zSamples + (radialSamples - 1) * (1 + (zSamples - 3) * 5));
// System.out.println("Vertext count=" + getVertexCount());
setVertexBuffer(BufferUtils.createVector3Buffer(getVertexBuffer(), getVertexCount()));
final FloatBuffer vb = getVertexBuffer();
// new
setColorBuffer(BufferUtils.createColorBuffer(getVertexCount()));
// allocate normals if requested
setNormalBuffer(BufferUtils.createVector3Buffer(getNormalBuffer(), getVertexCount()));
// allocate texture coordinates
setTextureCoords(new TexCoords(BufferUtils.createVector2Buffer(getVertexCount())));
// 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.
float[] afSin = new float[(radialSamples + 1)];
float[] afCos = new float[(radialSamples + 1)];
for (int iR = 0; iR < radialSamples; iR++) {
final float fAngle = FastMath.HALF_PI * fInvRS * iR;
afCos[iR] = FastMath.cos(fAngle);
afSin[iR] = FastMath.sin(fAngle);
}
afSin[radialSamples] = afSin[0];
afCos[radialSamples] = afCos[0];
final Vector3f tempVa = new Vector3f();
final Vector3f tempVb = new Vector3f();
final Vector3f tempVc = new Vector3f();
// generate the sphere itself
int vertexNumber = 0;
int vertexIndex = 0;
int textureIndex = 0;
Vector3f kNormal = new Vector3f();
for (int iZ = 1; iZ < (zSamples - 1); iZ++) {
float fZFraction = -1.0f + fZFactor * iZ; // in (-1,1)
float fZ = radius * fZFraction;
// compute center of slice
final Vector3f kSliceCenter = tempVb.set(center);
kSliceCenter.z += fZ;
// compute radius of slice
final float fSliceRadius = FastMath.sqrt(FastMath.abs(radius * radius - fZ * fZ)) / 1.0f /* divise la hauteur par x */;
// compute slice vertices with duplication at end point
for (int iR = 0; iR < radialSamples; iR++) {
//final float fRadialFraction = iR * fInvRS; // in [0,1)
final Vector3f kRadial = tempVc.set(afCos[iR], afSin[iR], 0);
kRadial.mult(fSliceRadius, tempVa);
kNormal.set(kSliceCenter.x + tempVa.x, kSliceCenter.y + tempVa.y, kSliceCenter.z + tempVa.z);
kNormal.subtractLocal(center);
kNormal.normalizeLocal();
// vertices + colors + normals
vertexNumber++;
vb.put(kSliceCenter.x + tempVa.x).put(kSliceCenter.y + tempVa.y).put(kSliceCenter.z + tempVa.z);
vertexIndex += 3;
getColorBuffer().put(SPHERE_COLOR.r).put(SPHERE_COLOR.g).put(SPHERE_COLOR.b).put(SPHERE_COLOR.a);
getNormalBuffer().put(kNormal.x).put(kNormal.y).put(kNormal.z);
vertexNumber++;
vb.put(kSliceCenter.x + tempVa.x).put(kSliceCenter.y + tempVa.y).put(kSliceCenter.z + tempVa.z);
vertexIndex += 3;
getColorBuffer().put(SPHERE_COLOR.r).put(SPHERE_COLOR.g).put(SPHERE_COLOR.b).put(SPHERE_COLOR.a);
getNormalBuffer().put(kNormal.x).put(kNormal.y).put(kNormal.z);
if (iR > 0 && iR < radialSamples - 1) {
vertexNumber++;
vb.put(kSliceCenter.x + tempVa.x).put(kSliceCenter.y + tempVa.y).put(kSliceCenter.z + tempVa.z);
vertexIndex += 3;
getColorBuffer().put(SPHERE_COLOR.r).put(SPHERE_COLOR.g).put(SPHERE_COLOR.b).put(SPHERE_COLOR.a);
getNormalBuffer().put(kNormal.x).put(kNormal.y).put(kNormal.z);
}
if (iZ > 1 && iZ < zSamples - 2) {
// System.out.println(vertexNumber + ": haut 1 decale");
vertexNumber++;
vb.put(kSliceCenter.x + tempVa.x).put(kSliceCenter.y + tempVa.y).put(kSliceCenter.z + tempVa.z);
vertexIndex += 3;
getColorBuffer().put(SPHERE_COLOR.r).put(SPHERE_COLOR.g).put(SPHERE_COLOR.b).put(SPHERE_COLOR.a);
getNormalBuffer().put(kNormal.x).put(kNormal.y).put(kNormal.z);
if (iR > 0 && iR < radialSamples - 1) {
// System.out.println(vertexNumber + ": haut 2 decale");
vertexNumber++;
vb.put(kSliceCenter.x + tempVa.x).put(kSliceCenter.y + tempVa.y).put(kSliceCenter.z + tempVa.z);
vertexIndex += 3;
getColorBuffer().put(SPHERE_COLOR.r).put(SPHERE_COLOR.g).put(SPHERE_COLOR.b).put(SPHERE_COLOR.a);
getNormalBuffer().put(kNormal.x).put(kNormal.y).put(kNormal.z);
}
}
}
}
// south pole
vb.position(vertexIndex);
vb.put(center.x).put(center.y).put(center.z - radius);
getNormalBuffer().position(vertexIndex);
getNormalBuffer().put(0).put(0).put(-1);
getColorBuffer().put(SPHERE_COLOR.r).put(SPHERE_COLOR.g).put(SPHERE_COLOR.b).put(SPHERE_COLOR.a);
getTextureCoords().get(0).coords.position(textureIndex);
getTextureCoords().get(0).coords.put(0.5f).put(0.0f);
// north pole
vb.put(center.x).put(center.y).put(center.z + radius);
getNormalBuffer().put(0).put(0).put(1);
getColorBuffer().put(SPHERE_COLOR.r).put(SPHERE_COLOR.g).put(SPHERE_COLOR.b).put(SPHERE_COLOR.a);
getTextureCoords().get(0).coords.put(0.5f).put(1.0f);
originalVerticesCoords = BufferUtils.createVector3Buffer(getVertexCount());
vb.position(0);
originalVerticesCoords.put(vb);
tempExtrudedVertices = BufferUtils.createVector3Buffer(getVertexCount());
tempExtrudedColors = BufferUtils.createColorBuffer(getVertexCount());
}
/**
* sets the indices for rendering the sphere.
*/
private void setIndexData() {
// allocate connectivity
setTriangleQuantity(/* south and north */
(radialSamples - 1) * 2 +
/* normal faces */
2 /* 2 triangles per squared face */ * (zSamples - 3) * (radialSamples - 1) +
/* extrudable faces */
2 /* 2 triangles per squared face */ * (zSamples - 3) * (radialSamples - 1) * 5);
setIndexBuffer(BufferUtils.createIntBuffer(3 * getTriangleCount()));
// generate connectivity for "square" faces
int northPoleOffset = 0;
int iZStartOffset = -1;
for (int iZ = 0, iZStart = 0; iZ < (zSamples - 3); iZ++) {
int i0 = iZStart;
int i1 = i0 + 2 + (iZ > 0 && iZ < (zSamples - 3) ? 1 : 0);
iZStart += (iZ == 0 ? 4 + ((radialSamples - 2) * 3) : iZStartOffset + 2 * (radialSamples - 1));
if (iZStartOffset == -1) {
iZStartOffset = iZStart;
}
if (iZ == zSamples - 4) {
northPoleOffset = iZStart;
}
int i2 = iZStart;
int i3 = i2 + 2 + (iZ >= 0 && iZ < (zSamples - 4) ? 1 : 0);
for (int i = 0; i < radialSamples - 1; i++) {
getIndexBuffer().put(i0);
getIndexBuffer().put(i1);
getIndexBuffer().put(i2);
getIndexBuffer().put(i1);
getIndexBuffer().put(i3);
getIndexBuffer().put(i2);
/*
Z axis
/e0--/e2
/ | / |
i0_|_i2 |
| /e1--/e3
|/ |/
i1___i3
*/
// computing extruded faces
final int eFi0 = i0 + 1 + (i > 0 ? 1 : 0) + (iZ > 0 ? 1 : 0) + (iZ > 0 && i > 0 ? 1 : 0);
final int eFi1 = i1 + 1 + (iZ > 0 ? 1 : 0) + (iZ > 0 && i == 0 ? 1 : 0) + (iZ > 0 && i > 0 && i < (radialSamples - 2) ? 1 : 0);
final int eFi2 = i2 + 1 + (i > 0 ? 1 : 0);
final int eFi3 = i3 + 1;
squaredFaces.add(new SquaredFace(eFi0, eFi1, eFi2, eFi3));
// upper face
//System.out.println("2/ " + eFi0 + "," + eFi1 + "," + eFi2 + "," + eFi3);
getIndexBuffer().put(eFi0);
getIndexBuffer().put(eFi1);
getIndexBuffer().put(eFi2);
getIndexBuffer().put(eFi1);
getIndexBuffer().put(eFi3);
getIndexBuffer().put(eFi2);
// front face
getIndexBuffer().put(i1);
getIndexBuffer().put(i3);
getIndexBuffer().put(eFi1);
getIndexBuffer().put(i3);
getIndexBuffer().put(eFi3);
getIndexBuffer().put(eFi1);
// back face
getIndexBuffer().put(i0);
getIndexBuffer().put(i2);
getIndexBuffer().put(eFi0);
getIndexBuffer().put(i2);
getIndexBuffer().put(eFi2);
getIndexBuffer().put(eFi0);
// left face
getIndexBuffer().put(i0);
getIndexBuffer().put(i1);
getIndexBuffer().put(eFi0);
getIndexBuffer().put(i1);
getIndexBuffer().put(eFi1);
getIndexBuffer().put(eFi0);
// right face
getIndexBuffer().put(i2);
getIndexBuffer().put(i3);
getIndexBuffer().put(eFi2);
getIndexBuffer().put(i3);
getIndexBuffer().put(eFi3);
getIndexBuffer().put(eFi2);
i0 = i1;
i1 += 3 + (iZ > 0 && iZ < (zSamples - 3) ? 2 : 0);
i2 = i3;
i3 += 3 + (iZ >= 0 && iZ < (zSamples - 4) ? 2 : 0);
}
}
// south pole triangles
for (int i = 0, i0 = 0, i1 = 2; i < radialSamples - 1; i++) {
getIndexBuffer().put(i0);
getIndexBuffer().put(getVertexCount() - 2);
getIndexBuffer().put(i1);
i0 = i1;
i1 += 3;
}
// north pole triangles
for (int i = 0, i0 = northPoleOffset, i1 = northPoleOffset + 2; i < radialSamples - 1; i++) {
getIndexBuffer().put(i0);
getIndexBuffer().put(i1);
getIndexBuffer().put(getVertexCount() - 1);
i0 = i1;
i1 += 3;
}
}
/**
* Returns the center of this sphere.
*
* @return The sphere's center.
*/
public Vector3f getCenter() {
return center;
}
/**
* Sets the center of this sphere. Note that other information (such as
* geometry buffers and actual vertex information) is not changed. In most
* cases, you'll want to use setData()
*
* @param aCenter The new center.
* @see #setData
*/
public void setCenter(Vector3f aCenter) {
center = aCenter;
}
public float getRadius() {
return radius;
}
public void write(JMEExporter e) throws IOException {
super.write(e);
OutputCapsule capsule = e.getCapsule(this);
capsule.write(zSamples, "zSamples", 0);
capsule.write(radialSamples, "radialSamples", 0);
capsule.write(radius, "radius", 0);
capsule.write(center, "center", Vector3f.ZERO);
}
public void read(JMEImporter e) throws IOException {
super.read(e);
InputCapsule capsule = e.getCapsule(this);
zSamples = capsule.readInt("zSamples", 0);
radialSamples = capsule.readInt("radialSamples", 0);
radius = capsule.readFloat("radius", 0);
center = (Vector3f) capsule.readSavable("center", Vector3f.ZERO.clone());
}
public List<SquaredFace> squaredFaces = new ArrayList<SquaredFace>();
public void startExtrusion() {
originalVerticesCoords.position(0);
tempExtrudedVertices.position(0);
tempExtrudedVertices.put(originalVerticesCoords);
tempExtrudedColors.position(0);
final int size = getVertexCount();
for (int i = 0; i < size; i++) {
tempExtrudedColors.put(i * 4, SPHERE_COLOR.r).put(i * 4 + 1, SPHERE_COLOR.g).put(i * 4 + 2, SPHERE_COLOR.b).put(i * 4 + 3, SPHERE_COLOR.a);
}
}
public class SquaredFace {
public int v1;
public int v2;
public int v3;
public int v4;
public SquaredFace(final int v1, final int v2, final int v3, final int v4) {
this.v1 = v1;
this.v2 = v2;
this.v3 = v3;
this.v4 = v4;
}
}
public void extrude(int squaredFaceIndex, final float factor, final ColorRGBA color) {
final SquaredFace sq = squaredFaces.get(squaredFaceIndex);
final int v1Offset = sq.v1 * 3;
tempExtrudedVertices
.put(v1Offset, factor * getNormalBuffer().get(v1Offset) + center.x)
.put(v1Offset + 1, factor * getNormalBuffer().get(v1Offset + 1) + center.y)
.put(v1Offset + 2, factor * getNormalBuffer().get(v1Offset + 2) + center.z);
tempExtrudedColors.put(v1Offset + sq.v1, color.r).put(v1Offset + sq.v1 + 1, color.g).put(v1Offset + sq.v1 + 2, color.b);
final int v2Offset = sq.v2 * 3;
tempExtrudedVertices
.put(v2Offset, factor * getNormalBuffer().get(v2Offset) + center.x)
.put(v2Offset + 1, factor * getNormalBuffer().get(v2Offset + 1) + center.y)
.put(v2Offset + 2, factor * getNormalBuffer().get(v2Offset + 2) + center.z);
tempExtrudedColors.put(v2Offset + sq.v2, color.r).put(v2Offset + sq.v2 + 1, color.g).put(v2Offset + sq.v2 + 2, color.b);
final int v3Offset = sq.v3 * 3;
tempExtrudedVertices
.put(v3Offset, factor * getNormalBuffer().get(v3Offset) + center.x)
.put(v3Offset + 1, factor * getNormalBuffer().get(v3Offset + 1) + center.y)
.put(v3Offset + 2, factor * getNormalBuffer().get(v3Offset + 2) + center.z);
tempExtrudedColors.put(v3Offset + sq.v3, color.r).put(v3Offset + sq.v3 + 1, color.g).put(v3Offset + sq.v3 + 2, color.b);
final int v4Offset = sq.v4 * 3;
tempExtrudedVertices
.put(v4Offset, factor * getNormalBuffer().get(v4Offset) + center.x)
.put(v4Offset + 1, factor * getNormalBuffer().get(v4Offset + 1) + center.y)
.put(v4Offset + 2, factor * getNormalBuffer().get(v4Offset + 2) + center.z);
tempExtrudedColors.put(v4Offset + sq.v4, color.r).put(v4Offset + sq.v4 + 1, color.g).put(v4Offset + sq.v4 + 2, color.b);
}
public void finishExtrusion() {
getVertexBuffer().position(0);
tempExtrudedVertices.position(0);
getVertexBuffer().put(tempExtrudedVertices);
getColorBuffer().position(0);
tempExtrudedColors.position(0);
getColorBuffer().put(tempExtrudedColors);
}
}
Usage:
QuarterSphere q = new QuarterSphere("name", 10.0f, 10.0f, 5.0f);
q.setLightCombineMode(Spatial.LightCombineMode.Off);
rootNode.attachChild(q);
...
q.startExtrusion();
q.extrude( 10, 5.0f /* radius */ + 1.0f /* extrusion amount */, ColorRGBA.red);
q.finishExtrusion();
Sample of an Oracle instance: http://oraclefun.blogspot.com/2008/07/session-control-panel-r10.html