/** * 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.scenegraph; import java.nio.FloatBuffer; /** * The Morph leaf node permits an application to morph between multiple GeometryArrays. * The Morph node contains a single Appearance node, an array of GeometryArray objects, * and an array of corresponding weights. The Morph node combines these GeometryArrays * into an aggregate shape based on each GeometryArray's corresponding weight. * Typically, Behavior nodes will modify the weights to achieve various morphing effects. * <p> * The following restrictions apply to each GeometryArray object in the specified array of GeometryArray objects: * <p> * - All N geometry arrays must be of the same type (that is, the same subclass of GeometryArray). * <p> * - The vertexFormat, texCoordSetCount, and validVertexCount must be the same for all N geometry arrays. * <p> * - The texCoordSetMap array must be identical (element-by-element) for all N geometry arrays. * <p> * - For IndexedGeometryArray objects, the validIndexCount must be the same for all N geometry arrays. * <p> * - For GeometryStripArray objects, the stripVertexCounts array must be identical (element-by-element) for all N geometry arrays. * <p> * - For IndexedGeometryStripArray objects, the stripIndexCounts array must be identical (element-by-element) for all N geometry arrays. * <p> * For indexed geometry, the array lengths of each enabled vertex component (coord, color, normal, texcoord) * must be the same for all N geometry arrays. * For IndexedGeometryArray objects, the vertex arrays are morphed before the indexes are applied. * Only the indexes in the first geometry array (geometry[0]) are used when rendering the geometry. * * @author David Yazel */ public class Morph extends Shape3D { // only here for compatibility public static final int ALLOW_GEOMETRY_ARRAY_READ = 20; public static final int ALLOW_GEOMETRY_ARRAY_WRITE = 21; public static final int ALLOW_WEIGHTS_READ = 22; public static final int ALLOW_WEIGHTS_WRITE = 23; private Geometry[] geometryArrays = null; private double[] weights = null; private float[][] coordinateData = null; private float[][] normalData = null; private float[][] colorData = null; private float[][][] texCoordData = null; private float[][] refData; private float[] refWeights; private int[] refGeoms; public void setGeometryArrays( Geometry[] geometryArrays ) { if ( ( geometryArrays == null ) || ( geometryArrays.length == 0 ) ) throw new IllegalArgumentException( "Morph: Number of GeometryArrays should be non-zero" ); for ( int i = 1; i < geometryArrays.length; i++ ) { doErrorCheck( geometryArrays[ i ], geometryArrays[ i - 1 ] ); } this.geometryArrays = geometryArrays; buildCompatibleGeometry( geometryArrays[ 0 ] ); double[] weights = new double[ geometryArrays.length ]; weights[ 0 ] = 1.0; setWeights( weights ); } private void doErrorCheck( Geometry a1, Geometry a2 ) { if ( ( a1 == null ) || ( a2 == null ) ) throw new IllegalArgumentException( "Morph: All GeometryArrays must be non-null" ); if ( ( a1.getVertexFormat() != a2.getVertexFormat() ) || ( a1.getValidVertexCount() != a2.getValidVertexCount() ) ) throw new IllegalArgumentException( "Morph: All GeometryArrays must have same vertexFormat, same validVertexCount" ); int[] texMap1 = a1.getTexCoordSetMap(); int[] texMap2 = a2.getTexCoordSetMap(); if ( ( texMap1 != null ) && ( texMap2 != null ) ) { if ( texMap1.length != texMap2.length ) throw new IllegalArgumentException( "Morph: All GeometryArrays must have same texCoordSetMap length" ); for ( int i = 0; i < texMap1.length; i++ ) { if ( texMap1[ i ] != texMap2[ i ] ) throw new IllegalArgumentException( "Morph: All GeometryArrays must have same texCoordSetMap" ); } } else if ( ( texMap1 != null ) || ( texMap2 != null ) ) throw new IllegalArgumentException( "Morph: All GeometryArrays must have same non-null texCoordSetMap" ); } private void buildCompatibleGeometry( Geometry a ) { setGeometry( a.cloneNodeComponent( true ) ); coordinateData = new float[ geometryArrays.length ][]; for ( int j = 0; j < geometryArrays.length; j++ ) { coordinateData[ j ] = geometryArrays[ j ].getCoordRefFloat(); } if ( ( a.getVertexFormat() & Geometry.NORMALS ) != 0 ) { normalData = new float[ geometryArrays.length ][]; for ( int j = 0; j < geometryArrays.length; j++ ) { normalData[ j ] = geometryArrays[ j ].getNormalRefFloat(); } } else { normalData = null; } if ( a.hasColorAlpha() ) { colorData = new float[ geometryArrays.length ][]; for ( int j = 0; j < geometryArrays.length; j++ ) { colorData[ j ] = geometryArrays[ j ].getColorRefFloat(); } } else { colorData = null; } final int[] tuSetMap = a.getTexCoordSetMap(); if ( tuSetMap.length > 0 ) { texCoordData = new float[ tuSetMap.length ][][]; for ( int i = 0; i < tuSetMap.length; i++ ) { texCoordData[ i ] = new float[ geometryArrays.length ][]; for ( int j = 0; j < geometryArrays.length; j++ ) { texCoordData[ i ][ j ] = geometryArrays[ j ].getTexCoordRefFloat( tuSetMap[ i ] ); } } } else { texCoordData = null; } } public final Geometry getGeometryArray( int i ) { return ( geometryArrays[ i ] ); } /** * Sets this Morph node's morph weight vector. * The Morph node "weights" the corresponding GeometryArray by the amount specified. * The weights apply a morph weight vector component that creates the desired morphing effect. * The length of the weights parameter must be equal to the length of the array with which this * Morph node was created, otherwise an IllegalArgumentException is thrown. * * There is no requirement that sum of all weights should be equal to 1.0. */ public void setWeights( double[] weights ) { if ( weights.length != geometryArrays.length ) throw new IllegalArgumentException( "Morph: number of weights not same as number of GeometryArrays" ); if ( ( this.weights == null ) || ( this.weights.length != weights.length ) ) this.weights = new double[ weights.length ]; for ( int i = weights.length - 1; i >= 0; i-- ) this.weights[ i ] = weights[ i ]; updateGeometryData(); } /** * Note that we assume we are using only NIO buffers, i.e. GeomNioFloatData. */ private void doWeight( float[][] src, float[] weights, int n, GeomNioFloatData dstData ) { final FloatBuffer dst = dstData.getBuffer(); dst.rewind(); int dstCount = src[ 0 ].length; switch ( n ) { case 1: { float[] s0 = src[ 0 ]; float w0 = weights[ 0 ]; for ( int i = 0; i < dstCount; i++ ) dst.put( s0[ i ] * w0 ); } return; case 2: { float[] s0 = src[ 0 ]; float[] s1 = src[ 1 ]; float w0 = weights[ 0 ]; float w1 = weights[ 1 ]; for ( int i = 0; i < dstCount; i++ ) dst.put( s0[ i ] * w0 + s1[ i ] * w1 ); } return; case 3: { float[] s0 = src[ 0 ]; float[] s1 = src[ 1 ]; float[] s2 = src[ 2 ]; float w0 = weights[ 0 ]; float w1 = weights[ 1 ]; float w2 = weights[ 2 ]; for ( int i = 0; i < dstCount; i++ ) dst.put( s0[ i ] * w0 + s1[ i ] * w1 + s2[ i ] * w2 ); } return; default: { for ( int i = 0; i < dstCount; i++ ) { float v = src[ 0 ][ i ] * weights[ 0 ]; for ( int j = 1; j < n; j++ ) { v += src[ j ][ i ] * weights[ j ]; } dst.put( v ); } } } } private void updateGeometryData() { Geometry ga = getGeometry(); if ( ( refData == null ) || ( refData.length != geometryArrays.length ) ) { refData = new float[ geometryArrays.length ][]; refWeights = new float[ geometryArrays.length ]; refGeoms = new int[ geometryArrays.length ]; } int n = 0; for ( int i = 0; i < weights.length; i++ ) { float w = (float)weights[ i ]; if ( w != 0.0f ) { refWeights[ n ] = w; refGeoms[ n++ ] = i; } } if ( n == 0 ) return; // TODO Reimplement array changes via direct access to NIO buffer data //float[] dst; for ( int i = 0; i < n; i++ ) { refData[ i ] = coordinateData[ refGeoms[ i ] ]; } doWeight( refData, refWeights, n, ga.getCoordinatesData() ); ga.getCoordinatesData().setDirty( true ); if ( ( ga.getVertexFormat() & Geometry.NORMALS ) != 0 ) { for ( int i = 0; i < n; i++ ) { refData[ i ] = normalData[ refGeoms[ i ] ]; } doWeight( refData, refWeights, n, ga.getNormalsData() ); ga.getNormalsData().setDirty( true ); } if ( ga.hasColorAlpha() ) { for ( int i = 0; i < n; i++ ) { refData[ i ] = colorData[ refGeoms[ i ] ]; } doWeight( refData, refWeights, n, ga.getColorData() ); ga.getColorData().setDirty( true ); } int[] texCoordSetMap = ga.getTexCoordSetMap(); int texCoordSetCount = ( texCoordSetMap == null ) ? 0 : texCoordSetMap.length; if ( texCoordSetCount > 0 ) { for ( int j = 0; j < texCoordSetCount; j++ ) { for ( int i = 0; i < n; i++ ) { refData[ i ] = texCoordData[ j ][ refGeoms[ i ] ]; } doWeight( refData, refWeights, n, ga.getTexCoordsData( j ) ); ga.getTexCoordsData( j ).setDirty( true ); } } } public final int getGeometryArrayCount() { return ( geometryArrays.length ); } /** * Constructs a new Morph object with a null geometry array components and * a null appearance component. */ public Morph() { super(); } public Morph( Geometry[] geometryArrays ) { setGeometryArrays( geometryArrays ); } public Morph( Geometry[] geometryArrays, Appearance appearance ) { setGeometryArrays( geometryArrays ); setAppearance( appearance ); } }