/** * Make a donation http://sourceforge.net/donate/index.php?group_id=98797 * Microcrowd.com * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * Contact Josh DeFord jdeford@microcrowd.com */ package com.microcrowd.loader.java3d.max3ds.chunks; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.Set; import javax.media.j3d.Appearance; import javax.media.j3d.Geometry; import javax.media.j3d.Material; import javax.media.j3d.Shape3D; import javax.media.j3d.TransformGroup; import javax.vecmath.Color3f; import javax.vecmath.Point3f; import javax.vecmath.TexCoord2f; import javax.vecmath.Vector3f; import com.microcrowd.loader.java3d.max3ds.ChunkChopper; import com.microcrowd.loader.java3d.max3ds.ChunkMap; import com.sun.j3d.utils.geometry.GeometryInfo; import com.sun.j3d.utils.geometry.NormalGenerator; import com.sun.j3d.utils.geometry.Stripifier; /** * This chunk describes all the triangles that make up a mesh. * Each triangle is defined in terms of three indexes each of which * is a point reference to a vertex in the vertex list loaded * by the triangular mesh chunk. * After loading the Smoothing chunk the normals for the mesh * are generated accordingly. */ public class FacesDescriptionChunk extends Chunk { public static final Appearance DEFAULT_APPEARANCE; private Point3f[] currentVertices; private TexCoord2f[] textureTriangles; private PointMapper shareMap; static { DEFAULT_APPEARANCE= new Appearance(); Material defaultMaterial = new Material(); defaultMaterial.setAmbientColor(new Color3f(.5f, .5f, .5f)); //defaultMaterial.setDiffuseColor(new Color3f(.5f, .5f, .5f)); //defaultMaterial.setSpecularColor(new Color3f(.5f, .5f, .5f)); DEFAULT_APPEARANCE.setMaterial(defaultMaterial); } /** * Maintains a two way mapping between coordinates * and vertices. A coordinate to vertex is one to many * Vertex to coordinate is one to one. * In this class we maintain the definition that a coordinate * is a point in 3D space and a vertex is a coordinate serving * as one of three defining a face. */ private class PointMapper extends HashMap { private Set[] coordinateSet; /** * Constructs a PointMapper with a * the number of coordinates initialized to size. * @param size the number of coordinates in the set. */ public PointMapper(int size) { coordinateSet = new Set[size]; } /** * Adds an index for a coordinate to the set of vertices mapped * to that coordinate. All coordinates may have one or more vertices * that use them. * @param coordinate the coordinate being mapped to the vertexNum * @param vertexNum the number of the vertex using the coordinate */ public void addCoordinate(Point3f coordinate, int vertexNum) { Set sharedCoordinates = (Set)get(coordinate); if(sharedCoordinates == null) { sharedCoordinates = new HashSet(); put(coordinate, sharedCoordinates); } sharedCoordinates.add(new Integer(vertexNum)); coordinateSet[vertexNum] = sharedCoordinates; } /** * Gets all the coordinates for a particular vertex that * also share that vertex after the smoothing groups have been * accounted for. Any coordinates that are not both shared * by the vertex and do not share a smoothing group with the coordinate * will not be returned. * @param coordinateNum the number of the coordinate to get the set * of vertices for that share it. * @param smoothGroups the group of coordinates used to filter out the * non-shared vertices. */ public Set getSharedCoordinates(int coordinateNum, int[] smoothGroups) { Set returnSet = new HashSet(); Set sharingVertices = coordinateSet[coordinateNum]; Iterator vertices = sharingVertices.iterator(); int coordinateMask = smoothGroups[coordinateNum]; while(vertices.hasNext()) { Integer vertex = (Integer)vertices.next(); int nextMask = smoothGroups[vertex.intValue()]; if((nextMask & coordinateMask) != 0) { returnSet.add(vertex); } } return returnSet; } } /** * Reads the number of faces from the ChunkChopper. * For each face read three shorts representing * indices of vertices loaded by the TriangularMeshChunk * * @param chopper chopper the has the data */ public void loadData(ChunkChopper chopper) { int numFaces = chopper.getUnsignedShort(); shareMap = new PointMapper(numFaces*3); Point3f[] coordinates = (Point3f[])chopper.popData(ChunkMap.VERTEX_LIST); TexCoord2f[] texturePoints = (TexCoord2f[])chopper.popData(ChunkMap.TEXTURE_COORDINATES); currentVertices = new Point3f[numFaces * 3]; chopper.pushData(chopper.getID(), currentVertices); if (texturePoints != null) { textureTriangles = new TexCoord2f[numFaces * 3]; } for (int i = 0; i < numFaces; i++) { int vertexIndex = i * 3; int index0 = chopper.getUnsignedShort(); int index1 = chopper.getUnsignedShort(); int index2 = chopper.getUnsignedShort(); currentVertices[vertexIndex] = coordinates[index0]; currentVertices[vertexIndex + 1] = coordinates[index1]; currentVertices[vertexIndex + 2] = coordinates[index2]; shareMap.addCoordinate(coordinates[index0], vertexIndex); shareMap.addCoordinate(coordinates[index1], vertexIndex+1); shareMap.addCoordinate(coordinates[index2], vertexIndex+2); if (textureTriangles != null) { textureTriangles[vertexIndex] = texturePoints[index0]; textureTriangles[vertexIndex + 1] = texturePoints[index1]; textureTriangles[vertexIndex + 2] = texturePoints[index2]; } //This is a bit masked value that is used to determine which edges are visible... not needed. chopper.getUnsignedShort(); } } /** * Loads a mesh onto the scene graph with the specified data * from subchunks. * If there is no material, this will put a default * material on the shape. */ public void initialize(ChunkChopper chopper) { final String materialName = (String)chopper.popData(ChunkMap.FACES_MATERIAL); final int[] smoothGroups = (int[])chopper.popData(ChunkMap.SMOOTH); Shape3D shape = new Shape3D(); GeometryInfo geometryInfo = new GeometryInfo(GeometryInfo.TRIANGLE_ARRAY); geometryInfo.setCoordinates(currentVertices); TransformGroup transformGroup = (TransformGroup)chopper.getGroup(); transformGroup.addChild(shape); if (textureTriangles != null) { geometryInfo.setTextureCoordinateParams(1, 2); geometryInfo.setTextureCoordinates(0, textureTriangles); } if(materialName != null) { shape.setAppearance((Appearance)chopper.getNamedObject(materialName)); } else { shape.setAppearance(DEFAULT_APPEARANCE); } if(smoothGroups == null) { NormalGenerator normalGenerator = new NormalGenerator(); geometryInfo.recomputeIndices(); normalGenerator.generateNormals(geometryInfo); } else { Vector3f[] normals = generateNormals(currentVertices); Vector3f[] smoothNormals = smoothNormals(normals, shareMap, smoothGroups); geometryInfo.setNormals(smoothNormals); } new Stripifier().stripify(geometryInfo); shape.setGeometry(geometryInfo.getGeometryArray()); shape.setCapability(Geometry.ALLOW_INTERSECT); com.sun.j3d.utils.picking.PickTool.setCapabilities(shape, com.sun.j3d.utils.picking.PickTool.INTERSECT_FULL); currentVertices=null; textureTriangles=null; } /** * Takes all the normals for all the vertices and averages them with * normals with which they share a coordinate and at least one smooth group. * @param currentNormals the normals for each face. * @param sharedPoints the point mapper that will choose which points are * and which are not shared. * @param smoothGroups the indexed list of group masks loaded by the smooth chunk. * @return normals averaged among the shared vertices in their smoothing groups. */ public Vector3f[] smoothNormals(Vector3f[] currentNormals, PointMapper sharedPoints, int[] smoothGroups) { Vector3f[] smoothNormals = new Vector3f[currentNormals.length]; for(int i=0; i < currentNormals.length; i++) { Set otherPoints = sharedPoints.getSharedCoordinates(i, smoothGroups); if(otherPoints != null) { Vector3f[] sharedNormals = new Vector3f[otherPoints.size()]; Iterator pointIterator = otherPoints.iterator(); for(int j = 0; j < sharedNormals.length; j++) { sharedNormals[j] = currentNormals[((Integer)pointIterator.next()).intValue()]; } smoothNormals[i] = averageNormals(sharedNormals); } else { smoothNormals[i] = currentNormals[i]; } } return smoothNormals; } /** * Averages the normals provided in order to provide * smooth, noncreased appearances for meshes. * @param normals the normals that should be averaged * @return a normalized normal that can be used in place * of all the normals provided. */ public Vector3f averageNormals(Vector3f[] normals) { Vector3f newNormal = new Vector3f(); for(int i=0; i < normals.length; i++) { newNormal.add(normals[i]); } newNormal.normalize(); return newNormal; } /** * Generates normals for each vertex of each * face that are absolutely normal to the face. * @param point0 The first point of the face * @param point1 The second point of the face * @param point2 The third point of the face * @return the three normals that should be * used for the triangle represented by the parameters. */ private Vector3f[] generateNormals(Point3f points[]) { Vector3f[] normals = new Vector3f[points.length]; for(int i=0; i < normals.length;) { Vector3f normal = new Vector3f(); Vector3f v1 = new Vector3f(); Vector3f v2 = new Vector3f(); v1.sub(points[i+1], points[i]); v2.sub(points[i+2], points[i]); normal.cross(v1, v2); normal.normalize(); normals[i++] = new Vector3f(normal); normals[i++] = new Vector3f(normal); normals[i++] = new Vector3f(normal); } return normals; } }