[JME 2.0] Dynamic sphere face extrusion howto?

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?  :slight_smile:



3/ if yes, how?  :smiley:



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!  :smiley:



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