/**
* Copyright (c) 2003-2009, Xith3D Project Group all rights reserved.
*
* Portions based on the Java3D interface, Copyright by Sun Microsystems.
* Many thanks to the developers of Java3D and Sun Microsystems for their
* innovation and design.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* Neither the name of the 'Xith3D Project Group' nor the names of its
* contributors may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) A
* RISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE
*/
package org.xith3d.utility.geometry;
import java.util.Arrays;
import java.util.TreeSet;
import org.openmali.vecmath2.Point3f;
import org.openmali.vecmath2.Vector3f;
import org.xith3d.utility.geometry.nvtristrip.PrimitiveGroup;
import org.xith3d.utility.geometry.nvtristrip.TriStrip;
/**
* This class allows for optimalization of indexed geometry. In most cases it will be filled with
* GeometryCreator.fillGeometryInfo, processed and result geometry will be retrieved. Exact way for
* creating renderer-specific geometry should be implemented in subclasses of this class.
*
* @author YVG
*/
public class GeometryInfo
{
protected static final int STATE_UNKNOWN = 0;
protected static final int STATE_SPLIT = 1;
protected static final int STATE_MERGED = 2;
protected VertexData[] vertices;
protected int[] triangles;
protected int[] smoothGroups;
private int vertexCacheSize = TriStrip.CACHESIZE_GEFORCE1_2;
protected int state = STATE_UNKNOWN;
/**
* Constructs new GeometryInfo
*/
public GeometryInfo()
{
super();
}
/**
* This method computes normal for each face in flat mode - every vertex is duplicated for
* each face and assigned separate normal belonging to this face.
* As side effect of this method, indices are 'split'. Merging them will probably not do much,
* as vertices with same coords will have different normals because of flat shading.
*/
public void recalculateFlatNormals()
{
unweldVertices();
Vector3f v1 = new Vector3f();
Vector3f v2 = new Vector3f();
Vector3f faceNormal = new Vector3f();
int numFaces = triangles.length / 3;
// step through all the faces
for ( int face = 0; face < numFaces; face++ )
{
Point3f a = vertices[ triangles[ face * 3 ] ].coord;
Point3f b = vertices[ triangles[ face * 3 + 1 ] ].coord;
Point3f c = vertices[ triangles[ face * 3 + 2 ] ].coord;
v1.sub( b, a );
v2.sub( c, a );
faceNormal.cross( v1, v2 );
faceNormal.normalize();
vertices[ triangles[ face * 3 ] ].normal = new Vector3f( faceNormal );
vertices[ triangles[ face * 3 + 1 ] ].normal = new Vector3f( faceNormal );
vertices[ triangles[ face * 3 + 2 ] ].normal = new Vector3f( faceNormal );
}
}
/**
* This method computes smoothened normals for each vertex. Normal of each face from
* same smoothing group is averaged to compute value of vertex. This means that
* vertex will be duplicated if it belongs to faces belonging to more than one smooth group.
* After this operation, vertices will be mostly welded - unless there is a case where two neighbour faces
* from different smoothing group have same normal.
*/
public void recalculateSmoothGroupNormals()
{
if ( smoothGroups == null )
throw new IllegalStateException( "Missing smooth group data" );
for ( int i = 0; i < vertices.length; i++ )
{
vertices[ i ].normal = null;
}
unweldVertices();
for ( int i = 0; i < smoothGroups.length; i++ )
{
vertices[ triangles[ i * 3 ] ].smoothGroup = smoothGroups[ i ];
vertices[ triangles[ i * 3 + 1 ] ].smoothGroup = smoothGroups[ i ];
vertices[ triangles[ i * 3 + 2 ] ].smoothGroup = smoothGroups[ i ];
}
weldVertices();
Vector3f v1 = new Vector3f();
Vector3f v2 = new Vector3f();
Vector3f faceNormal = new Vector3f();
int numFaces = triangles.length / 3;
for ( int i = 0; i < vertices.length; i++ )
{
vertices[ i ].normal = new Vector3f();
}
// step through all the faces
for ( int face = 0; face < numFaces; face++ )
{
Point3f a = vertices[ triangles[ face * 3 ] ].coord;
Point3f b = vertices[ triangles[ face * 3 + 1 ] ].coord;
Point3f c = vertices[ triangles[ face * 3 + 2 ] ].coord;
v1.sub( b, a );
v2.sub( c, a );
faceNormal.cross( v1, v2 );
vertices[ triangles[ face * 3 ] ].normal.add( faceNormal );
vertices[ triangles[ face * 3 + 1 ] ].normal.add( faceNormal );
vertices[ triangles[ face * 3 + 2 ] ].normal.add( faceNormal );
}
for ( int i = 0; i < vertices.length; i++ )
{
vertices[ i ].normal.normalize();
vertices[ i ].smoothGroup = -1;
}
state = STATE_UNKNOWN;
}
/**
* Find vertices with same parameters and merge them into one. Allows sharing of data between faces,
* which is a requirement for performance benefit from GPU vertex cache.
*
* @see #unweldVertices
*/
public void weldVertices()
{
if ( state == STATE_MERGED )
return;
TreeSet< VertexData > nverts = new TreeSet< VertexData >();
for ( int i = 0; i < vertices.length; i++ )
{
nverts.add( vertices[ i ] );
}
VertexData[] ndata = nverts.toArray( new VertexData[ nverts.size() ] );
int[] nTriangles = new int[ triangles.length ];
for ( int i = 0; i < nTriangles.length; i++ )
{
nTriangles[ i ] = Arrays.binarySearch( ndata, vertices[ triangles[ i ] ] );
}
vertices = ndata;
triangles = nTriangles;
state = STATE_MERGED;
}
/**
* Duplicate vertex data for each face, so it is not shared between them. Allows for allocating
* per-face normal (flat shading).
*
* @see #weldVertices
*/
public void unweldVertices()
{
if ( state == STATE_SPLIT )
return;
VertexData[] ndata = new VertexData[ triangles.length ];
for ( int i = 0; i < triangles.length; i++ )
{
ndata[ i ] = new VertexData( vertices[ triangles[ i ] ] );
triangles[ i ] = i;
}
vertices = ndata;
state = STATE_SPLIT;
}
/**
* This method reorders face indices to fit well into vertex cache of GPU. This operation is meaningful
* if you plan to use non-strip triangle array later - in other case, use one of strip generation methods,
* which are cache-aware by default.
* Note: this method destroys smoothGroups info and it cannot be recovered, as order of faces is changed.
* If you want to use smoothing group info for generating normals, you need to do it _before_ you call this
* method.
*
* @see #setVertexCacheSize
*/
public void optimizeTrianglesForCache()
{
TriStrip ts = new TriStrip();
ts.setCacheSize( getVertexCacheSize() );
ts.setListsOnly( true );
PrimitiveGroup[] pg = ts.generateStrips( triangles );
assert ( pg.length == 1 );
assert ( pg[ 0 ].type == PrimitiveGroup.PT_LIST );
smoothGroups = null;
triangles = pg[ 0 ].getTrimmedIndices();
}
/**
* Create continous triangle strip with separate substrips connected by degenerate triangles.
* Please note that unless vertices are welded before (explicitly by weldVertices or implictly by
* recalculateSmoothGroupNormals) stripification will not help with perfomance, as same vertex will
* have different index for different face.
*
* @return indices of triangle strip
*/
public int[] createContinousStrip()
{
TriStrip ts = new TriStrip();
ts.setCacheSize( getVertexCacheSize() );
ts.setListsOnly( false );
ts.setMinStripSize( 0 );
ts.setStitchStrips( true );
PrimitiveGroup[] pg = ts.generateStrips( triangles );
assert ( pg.length == 1 );
assert ( pg[ 0 ].type == PrimitiveGroup.PT_STRIP );
return pg[ 0 ].getTrimmedIndices();
}
/**
* @return the vertexCacheSize
*/
public int getVertexCacheSize()
{
return vertexCacheSize;
}
/**
* This method sets size of vertex cache on gpu for which strips/triangle lists should be optimized.
* This is the "actual" cache size, so 24 for GeForce3 and 16 for GeForce1/2 You may
* want to play around with this number to tweak performance. Default value: 16
* In case of doubt it is better to underestimate size of cache.
* If you don't care about vertex cache and want strips as long as possible, put very high value here -
* but be warned, because stripification algorithm is O(n^2*m) [doublecheck - is it true?], where n
* is number of indices, and m is size of vertexCacheSize. On the other hand, with too small value, you will
* get too many small strips and cost of degenerate triangles will rise.
*
* @param vertexCacheSize The vertexCacheSize to set.
*/
public void setVertexCacheSize( int vertexCacheSize )
{
this.vertexCacheSize = vertexCacheSize;
}
}