Normal generator & Solids of revolution for jME

Well, after saying it many times, I finally got to start my NormalGenerator class. For now, it only accepts TRIANGLE_STRIP and TRIANGLE_FAN batches, does not support tex coords, and does not allow inter-batch normal smoothing, but these features are coming later (this week perhaps  ;)).



By the way The solids of revolution were developed kind-of to test the normal generator, but I figured I would just add them here if someone likes them. They will include UV coords in the future as well.



SolidOfRevolution.java


/*
 * SolidOfRevolution.java
 *
 * Created on August 24, 2007, 9:17 PM
 *
 */

import com.jme.math.FastMath;
import com.jme.scene.batch.GeomBatch;
import com.jme.scene.batch.TriangleBatch;
import java.nio.*;

/**
 *
 * @author Edgar A. Duenez-Guzman
 */
public class SolidOfRevolution
{
   
    /** Creates a new instance of SolidOfRevolution */
    private SolidOfRevolution()
    {
    }
    public static GeomBatch createBatch( float x0, float x1, Function f ) { return createBatch( x0, x1, f, 1 ); }
    public static GeomBatch createBatch( float x0, float x1, Function f, int xsamples ) { return createBatch( x0, x1, f, xsamples, 8 ); }
    public static GeomBatch createBatch( float x0, float x1, Function f, int xsamples, int radsamples )
    {
        TriangleBatch b = new TriangleBatch();
        float y0, y1, deltaX = (x1 - x0)/xsamples, tmp0, tmp1;
        b.setMode( TriangleBatch.TRIANGLE_STRIP );
        FloatBuffer vert = ByteBuffer.allocateDirect( 12*(xsamples+1)*radsamples ).asFloatBuffer();
        IntBuffer indx = ByteBuffer.allocateDirect( 8*xsamples*(radsamples + 1) ).asIntBuffer();
        //FloatBuffer norm = ByteBuffer.allocateDirect( 24*radial ).asFloatBuffer();
        b.setVertexBuffer( vert );
        //b.setNormalBuffer( norm );
        b.setIndexBuffer( indx );

        for( int j = 0; j < xsamples; j++ )
        {
            tmp0 = x0 + deltaX*j;
            tmp1 = x0 + deltaX*(j+1);
            y0 = f.eval( tmp0 );
            y1 = f.eval( tmp1 );
            for( int i = 0; i < radsamples; i++ )
            {
                vert.put( tmp0 );
                vert.put( FastMath.sin( 2*FastMath.PI*i/radsamples )*y0 );
                vert.put( FastMath.cos( 2*FastMath.PI*i/radsamples )*y0 );

                indx.put( radsamples*j + i );
                indx.put( radsamples*(j+1) + i );
            }
            indx.put( radsamples*j );
            indx.put( radsamples*(j+1) );
        }
        //last loop of points.
        y1 = f.eval( x1 );
        for( int i = 0; i < radsamples; i++ )
        {
            vert.put( x1 );
            vert.put( FastMath.sin( 2*FastMath.PI*i/radsamples )*y1 );
            vert.put( FastMath.cos( 2*FastMath.PI*i/radsamples )*y1 );
        }
        return b;
    }
    public static interface Function
    {
        public float eval( float x );
    }
}




TestNormalGenerator.java

/*
 * TestNormalGenerator.java
 *
 * Created on August 18, 2007, 7:36 PM
 *
 * To change this template, choose Tools | Template Manager
 * and open the template in the editor.
 */

import com.jme.app.*;
import com.jme.math.*;
import com.jme.scene.*;
import com.jme.scene.batch.*;
import com.jme.scene.shape.*;
import java.nio.*;
import java.util.ArrayList;

/**
 *
 * @author Edgar A. Duenez-Guzman
 */
public class TestNormalGenerator extends SimpleGame
{
    private Sphere reference;
    private TriMesh mesh;
    private TriangleBatch batch[] = new TriangleBatch[2];

    private boolean flag = true;
    /** Creates a new instance of TestNormalGenerator */
    public TestNormalGenerator()
    {
        super();
    }
    protected void simpleInitGame()
    {
        reference = new Sphere( "Object", 8, 8, 1.0f );
        reference.setLocalTranslation( -3.0f, 0, 0 );
        rootNode.attachChild( reference );
        mesh = new TriMesh( "Shape" );
        mesh.clearBatches();
        mesh.addBatch( SolidOfRevolution.createBatch( 1, 4, new SolidOfRevolution.Function() {
                public float eval( float x ) { return FastMath.exp(x*0.7f-1.0f)/(x-0.5f); }
            }, 16, 8 )
        );
        mesh.addBatch( SolidOfRevolution.createBatch( 6, 6.5f, new SolidOfRevolution.Function() {
                public float eval( float x ) { return FastMath.sqrt(x); }
            } )
        );
        rootNode.attachChild( mesh );
    }
    protected void simpleUpdate()
    {
        if( flag && timer.getTime() > 3000 )
        {
            flag = false;
            System.out.println( "Calling normal generator" );
            //NormalGenerator.genNormals( mesh.getBatch(0) );
            ArrayList<GeomBatch> batches = new ArrayList<GeomBatch>();
            NormalGenerator.splitBatch( mesh.getBatch(0), batches, 0.7f );
            System.out.println( "Normals generated!" );
            TriMesh newMesh = new TriMesh( "New Mesh" );
            newMesh.clearBatches();
            newMesh.setLocalTranslation( 0.0f, 10.0f, 0.0f );
            for( int i = 0; i < batches.size(); i++ )
                newMesh.addBatch( batches.get(i) );
            rootNode.attachChild( newMesh );
            rootNode.updateGeometricState( tpf, true );
        }
    }
    public static void main( String args[] )
    {
        TestNormalGenerator t = new TestNormalGenerator();
        t.start();
    }
}

Awesome, but any submission is incomplete without screenshots. :wink:

Alright… here they go.



Here with a crease (folding angle) of 0.7f





Here with a crease (folding angle) of 3.7f (i.e. never fold)





Here with a crease (folding angle) of 3.7f and more radial samples.


Pretty.  :smiley:

I have updated SolidOfRevolution to be a much more flexible framework.



It now allows for autocomputing of texture coordinates, normals (not with NormalGenerator), and the points can now be provided directly (not only through functions). As a result, you can now generate much more interesting solids:



import com.jme.math.FastMath;
import com.jme.scene.batch.*;
import com.jme.util.geom.BufferUtils;
import java.awt.geom.Point2D;
import java.nio.*;

/**
 *
 * @author Edgar A. Duenez-Guzman
 */
public class SolidOfRevolution
{
    public static final int TRIANGLES = 0;
    public static final int QUADS = 1;
    public static final int TEXTURES = 2;
    public static final int NORMALS = 4;

    /** Creates a new instance of SolidOfRevolution */
    private SolidOfRevolution() {}

    private static float approximateNormalAngle( Point2D.Float[] pts, int i )
    {
        float sum = 0.0f;
        int count = 0;
        if( i > 0 )
        {
            sum += FastMath.atan2( pts[i].y - pts[i-1].y, pts[i].x - pts[i-1].x ) + FastMath.HALF_PI;
            count++;
        }
        if( i < pts.length-1 )
        {
            sum += FastMath.atan2( pts[i+1].y - pts[i].y, pts[i+1].x - pts[i].x ) + FastMath.HALF_PI;
            count++;
        }
        if( count == 0 )
            return FastMath.HALF_PI;
        return sum / count;
    }
    public static GeomBatch createBatch( float x0, float x1, Function f ) { return createBatch( TRIANGLES | NORMALS | TEXTURES, x0, x1, f, 4, 16 ); }
    public static GeomBatch createBatch( int type, float x0, float x1, Function f ) { return createBatch( type, x0, x1, f, 4, 16 ); }
    public static GeomBatch createBatch( float x0, float x1, Function f, int xsamples, int radsamples ) { return createBatch( TRIANGLES | NORMALS | TEXTURES, x0, x1, f, xsamples, radsamples ); }
    public static GeomBatch createBatch( int type, float x0, float x1, Function f, int xsamples, int radsamples )
    {
        float deltaX = (x1 - x0)/xsamples;
        Point2D.Float[] pts = new Point2D.Float[ xsamples+1 ];
        for( int j = 0; j < xsamples + 1; j++ )
            pts[j] = new Point2D.Float( x0 + deltaX*j, f.eval( x0 + deltaX*j ) );
        return createBatch( type, pts, radsamples );
    }
    public static GeomBatch createBatch( Point2D.Float[] pts ) { return createBatch( TRIANGLES | NORMALS | TEXTURES, pts, 16 ); }
    public static GeomBatch createBatch( int type, Point2D.Float[] pts ) { return createBatch( type, pts, 16 ); }
    public static GeomBatch createBatch( Point2D.Float[] pts, int radsamples ) { return createBatch( TRIANGLES | NORMALS | TEXTURES, pts, radsamples ); }
    public static GeomBatch createBatch( int type, Point2D.Float[] pts, int radsamples )
    {
        float n0 = 0, n1 = 1, angle;
        FloatBuffer vert = BufferUtils.createFloatBuffer( 3*pts.length*radsamples );
        IntBuffer indx = BufferUtils.createIntBuffer( 2*(pts.length-1)*(radsamples + 1) );
        FloatBuffer norm = null, text = null;

        if( (type & NORMALS) == NORMALS )
            norm = BufferUtils.createFloatBuffer( 3*pts.length*radsamples );
        if( (type & TEXTURES) == TEXTURES )
            text = BufferUtils.createFloatBuffer( 2*pts.length*radsamples );

        for( int j = 0; j < pts.length; j++ )
        {
            //Finds the angle in radians of the approximate normal to the function in the provided point.
            if( (type & NORMALS) == NORMALS )
            {
                angle = approximateNormalAngle( pts, j );
                n0 = FastMath.cos( angle );
                n1 = FastMath.sin( angle );
            }
            for( int i = 0; i < radsamples; i++ )
            {
                vert.put( pts[j].x );
                vert.put( FastMath.sin( 2*FastMath.PI*i/radsamples )*pts[j].y );
                vert.put( FastMath.cos( 2*FastMath.PI*i/radsamples )*pts[j].y );

                if( (type & NORMALS) == NORMALS )
                {
                    norm.put( n0 );
                    norm.put( FastMath.sin( 2*FastMath.PI*i/radsamples )*n1 );
                    norm.put( FastMath.cos( 2*FastMath.PI*i/radsamples )*n1 );
                }
                if( (type & TEXTURES) == TEXTURES )
                {
                    text.put( ( (float)j )/( pts.length-1 ) );
                    text.put( ( (float)i )/radsamples );
                }

                if( j < pts.length-1 )
                {
                    indx.put( radsamples*j + i );
                    indx.put( radsamples*(j+1) + i );
                }
            }
            if( j < pts.length-1 )
            {
                indx.put( radsamples*j );
                indx.put( radsamples*(j+1) );
            }
        }

        if( (type & QUADS) == QUADS )
        {
            QuadBatch qb = new QuadBatch();
            qb.setMode( QuadBatch.QUAD_STRIP );
            qb.setVertexBuffer( vert );
            if( (type & NORMALS) == NORMALS )
                qb.setNormalBuffer( norm );
            if( (type & TEXTURES) == TEXTURES )
                qb.setTextureBuffer( text, 0 );
            qb.setIndexBuffer( indx );
            return qb;
        }
        else
        {
            TriangleBatch tb = new TriangleBatch();
            tb.setMode( TriangleBatch.TRIANGLE_STRIP );
            tb.setVertexBuffer( vert );
            if( (type & NORMALS) == NORMALS )
                tb.setNormalBuffer( norm );
            if( (type & TEXTURES) == TEXTURES )
                tb.setTextureBuffer( text, 0 );
            tb.setIndexBuffer( indx );
            return tb;
        }
    }

    public static interface Function
    {
        public float eval( float x );
    }
}



Screen:

Hey Dunez…Awsome!

Hi @duenez!

.

Do you think this can be updated to jme3?

Also, do you believe it could work with boolean operations on mesh?

.

PS.: this is what I am trying to fix:

http://hub.jmonkeyengine.org/groups/graphics/forum/topic/clueless-texture-distortion/#post-130254