/*********************************************************************** * mt4j Copyright (c) 2008 - 2009 C.Ruff, Fraunhofer-Gesellschaft All rights reserved. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. * ***********************************************************************/ package org.mt4j.util; import java.util.ArrayList; import org.apache.log4j.ConsoleAppender; import org.apache.log4j.Level; import org.apache.log4j.Logger; import org.apache.log4j.SimpleLayout; import org.mt4j.components.visibleComponents.GeometryInfo; import org.mt4j.util.math.ToolsGeometry; import org.mt4j.util.math.ToolsMath; import org.mt4j.util.math.Vector3D; import org.mt4j.util.math.Vertex; import processing.core.PApplet; /** * This class can be used to generate normals for indexed triangles geometry. * <br><li>It duplicates a vertex that has more than 1 texture coordinate. * So texture with indexed geometry can still be used. * <br><li>It also duplicates equal vertices that get assigned different normals in the calculation process according * to the face they belong to. * <p> * * @author C.Ruff */ public class TriangleNormalGenerator { /** The Constant logger. */ private static final Logger logger = Logger.getLogger(TriangleNormalGenerator.class.getName()); static{ logger.setLevel(Level.ERROR); SimpleLayout l = new SimpleLayout(); ConsoleAppender ca = new ConsoleAppender(l); logger.addAppender(ca); } /** The null vect. */ private Vertex nullVect; /** Use these for console debug info. */ // private boolean debug; /** The use normals equal to face. */ private boolean useNormalsEqualToFace; /** The use equal neighbor normals again. */ private boolean useEqualNeighborNormalsAgain; /** * Constructor. */ public TriangleNormalGenerator(){ nullVect = new Vertex(0,0,0, -1, -1); useNormalsEqualToFace = true; useEqualNeighborNormalsAgain = true; } /** * Generates smooth normals for a triangle geometry array. * * @param pa the pa * @param geometryInfo the geometry info * * @return the geometry info * * the geometry array with normals */ public GeometryInfo generateTriangleNormals(PApplet pa, GeometryInfo geometryInfo){ return this.generateTriangleNormals(pa, geometryInfo, 180); } /** * Generates normals for the provided geometry info according to the crease angle. * <br>A crease angle of 180 will result in a all smoothed model, smoothing each vertex normal * across all its neighbor faces' face normals. * <br>A crease angle of zero (0) will result in a flat shaded geometry. Only face normals will * be used then. * <br>A crease angle of 89 will create hard edges at 90 degree angle faces and smooth faces with * less then 90 degree normals. This would be ideal to generate normals for a cube. * <br>The best crease angle for a model has to be found by testing different angles. * * <br><strong>Note:</strong>The geometry has to represent a TRIANGLES array! * <br><strong>Note:</strong>The stroke color information gets lost during the process and is reset * to the default color. Set it again if needed with <code>setStrokeColorAll</code>. * * @param pa the pa * @param geometryInfo the geometry info * @param creaseAngle the crease angle * * @return the geometry info * * the geometry array with normals */ public GeometryInfo generateTriangleNormals(PApplet pa, GeometryInfo geometryInfo, float creaseAngle ){ Vertex[] vertices = geometryInfo.getVertices(); //Gen texcoord array float[][] texCoords = new float[vertices.length][2]; for (int i = 0; i < vertices.length; i++) { Vertex v = vertices[i]; texCoords[i][0] = v.getTexCoordU(); texCoords[i][1] = v.getTexCoordV(); } //Gen or get indices array int[] indices = null; if (!geometryInfo.isIndexed()){ indices = new int[vertices.length]; for (int i = 0; i < vertices.length; i++) { indices[i] = i; } }else{ indices = geometryInfo.getIndices(); } //Gen texcoord array as same as indices array int[] texIndices = new int[indices.length]; for (int i = 0; i < indices.length; i++) { texIndices[i] = indices[i]; } //Generate normals GeometryInfo geom = null; if (creaseAngle == 180){ geom = this.generateSmoothNormals(pa, vertices, indices, texCoords, texIndices, creaseAngle, false, false); }else{ geom = this.generateCreaseAngleNormals(pa, vertices, indices, texCoords, texIndices, creaseAngle, false, false); } //Reset indices if they werent set before,too //Reconstruct the passed in geometryinfo if (!geometryInfo.isIndexed()){ geometryInfo.reconstruct(geom.getVertices(), geom.getNormals(), null, true, false, null); }else{ geometryInfo.reconstruct(geom.getVertices(), geom.getNormals(), geom.getIndices(), true, false, null); } geom = null; return geometryInfo; } /** * Generates triangle normals, smoothed acroos all neighbor faces. * <br>Also dissolves multiple texturecoordinates belonging to the same vertex by * duplicating them. * * @param pa the pa * @param originalVertices the original vertices * @param originalIndices the original indices * @param originalTexCoords the original tex coords * @param originalTexIndices the original tex indices * @param creaseAngle the crease angle * @param flipTextureY the flip texture y * @param flipTextureX the flip texture x * * @return the geometry info */ public GeometryInfo generateSmoothNormals(PApplet pa, Vertex[] originalVertices, int[] originalIndices, float[][] originalTexCoords, int[] originalTexIndices, float creaseAngle, boolean flipTextureY, boolean flipTextureX){ int newDuplicatesWithDiffTexCoordsCreated = 0; int newDuplicatesWithDiffNormalCreated = 0; logger.debug("-> Loading all smoothed model."); ArrayList<VertexData> vertexDatas = new ArrayList<VertexData>(originalVertices.length); ArrayList<MyFace> faces = new ArrayList<MyFace>(Math.round(originalIndices.length/3)); //Init and fill vertexdata list with as many as vertices for (int i = 0; i < originalVertices.length; i++) { //Vertex v = vertices[i]; VertexData vd = new VertexData(); vd.setArrayIndex(i); vertexDatas.add(vd); } int pp0 = 0; int pp1 = 0; int pp2 = 0; int tIndexPP0 = 0; int tIndexPP1 = 0; int tIndexPP2 = 0; //GO through indices array and create a face for every three indices (must be triangle array!) for (int i = 0; i < originalIndices.length/3; i++) { //int currentIndex = indices[i]; //Next 3 vertex indices as the new faces pointers pp0 = originalIndices[i*3]; pp1 = originalIndices[i*3+1]; pp2 = originalIndices[i*3+2]; if (originalTexCoords.length > 0){ //Next 3 texture texture indices //(vertex and texture indices indexed in the same order) tIndexPP0 = originalTexIndices[i*3]; tIndexPP1 = originalTexIndices[i*3+1]; tIndexPP2 = originalTexIndices[i*3+2]; } MyFace myFace = new MyFace(); myFace.p0 = pp0; myFace.p1 = pp1; myFace.p2 = pp2; int vertexPointer = pp0; int texturePointer = tIndexPP0; for (int j = 0; j < 3; j++) { if (j == 0) { vertexPointer = pp0; texturePointer = tIndexPP0; } else if(j == 1){ vertexPointer = pp1; texturePointer = tIndexPP1; } else if(j == 2){ vertexPointer = pp2; texturePointer = tIndexPP2; } //Get the vertexdata at the index, the face points to VertexData myVertexData = vertexDatas.get(vertexPointer); Vertex newVertex = new Vertex( originalVertices[vertexPointer].x, originalVertices[vertexPointer].y, originalVertices[vertexPointer].z, originalVertices[vertexPointer].getR(), originalVertices[vertexPointer].getG(), originalVertices[vertexPointer].getB(), originalVertices[vertexPointer].getA() ); // Create texcoords and add to vertex float[] tex = new float[2]; if (originalTexCoords.length > 0){ tex[0] = originalTexCoords[texturePointer][0]; tex[1] = originalTexCoords[texturePointer][1]; if (flipTextureY){ tex[1] = 1.0f-tex[1]; } if (flipTextureX){ tex[0] = 1.0f-tex[0]; } newVertex.setTexCoordU(tex[0]); newVertex.setTexCoordV(tex[1]); } //Check if the vertex data at the pointer is empty thats the case before first time a vertex is added to it here if (myVertexData.getVertex() == null){ myVertexData.setVertex(newVertex); myVertexData.addNeighborFace(myFace); logger.debug("vdP" + j + " vertex in vertexData not initialzied -> set it"); // logger.debug("vdP0 is empty -> adding first vertex, texture tuple"); }else{//Vertex data at index already contains one or more vertices //Check if the vertex data at the index contains a vertex that is equal to the new vertex and its texture information //-> if true, just add the vertex to that vertexdata and the face it also is in //-> if false, check if a duplicate vertexdata is registered in the vertexdata the face points to // -> if true, add the vertex to the duplicate, change face index to the duplicates index // -> if false, create new vertexdata at end of list, add the vertex and texcoords of the vertex, and register // it as a duplicate in the original vertexdata, change the face index to the new duplicates index if (myVertexData.equalsVertex(newVertex)){ //Register das face mit dem vertex data myVertexData.addNeighborFace(myFace); logger.debug("vdP" + j + "already CONTAINS a vertex with same coords and texture information -> do nothing, just a the current face to its neighborlist"); }else{ //Check if already duplicates were created, maybe with the same vertex and texture information, //then we have to add the vertex to that vertexdata rather than creating a new one int duplicateIndex = myVertexData.getVertDuplicateSameVertDiffTextureCoordListIndex(newVertex); if (duplicateIndex != -1){ //Es gibt schon ein duplicate mit gleichen tex coords wie dieses hier, adde bei duplicateIndex //Change face pointer of p0 to the duplicates index if (j == 0) myFace.p0 = duplicateIndex; else if(j == 1) myFace.p1 = duplicateIndex; else if(j == 2) myFace.p2 = duplicateIndex; //wenn wir orginal hinzuf�gen wird auch allen duplicates neighbor face zugef�gt. myVertexData.addNeighborFace(myFace); logger.debug("vdP" + j + "has different texture coordiantes but a already created duplicate has the same -> change this face pointer to the duplicate one"); }else{//there was no duplicate created with the same texture coords yet -> create one! //Neuen Vertex machen, an original vertices list anh�ngen, neuen index in face auf diesen setzen, face adden VertexData newVertexData = new VertexData(); //Add the vertex information to the newly created vertexdata newVertexData.setVertex(newVertex); //Get new index at end of vertex list int newIndexOfNewVertexData = vertexDatas.size(); //Change the index of the face to the index the new Vdata is created at if (j == 0) myFace.p0 = newIndexOfNewVertexData; else if(j == 1) myFace.p1 = newIndexOfNewVertexData; else if(j == 2) myFace.p2 = newIndexOfNewVertexData; //Tell the vertexdata the index it is in the overall data newVertexData.setArrayIndex(newIndexOfNewVertexData); //Add new vertex data at the end of the list of all vertexdatas vertexDatas.add(newVertexData); //Inform the original vertexdata, that a duplicate with diff tex coords was created and register it myVertexData.registerCreatedDuplicateDiffTexCoords(newVertexData); //Adde face zu orginal face -> damit wird auch duplicates und dem neuen hinzugef�gt, //wenn wir es vorher mit registerDuplicate am orginal registiert haben! myVertexData.addNeighborFace(myFace); //copy the face list the vertex is contained in of the original //to the duplicate (for normal generation later, so they are still neighbors)! newVertexData.addNeighborFaces(myVertexData.getFacesContainedIn()); logger.debug("vdP" + j + "isnt empty but DOESENT CONTAIN (also no duplicate contains) a vertex with same coords and texture information -> creating new V.D. at: " + newIndexOfNewVertexData); newDuplicatesWithDiffTexCoordsCreated++; }//end if duplicate exists }//end if vertexdata already contains Vnew }//end if vertexdata is empty }//end for p0,p1,p2 //Calculate the face's normal vector myFace.calcFaceNormal(vertexDatas); myFace.index = faces.size(); //Add face to facelist faces.add(myFace); } logger.debug("-> Processed duplicate vertex/texture points."); //Create arrays Vertex[] newVertices = new Vertex[vertexDatas.size()]; Vector3D[] normals = new Vector3D[vertexDatas.size()]; int[] newIndices = new int[faces.size()*3]; /* * Go through the final faces list and fill vertex/newIndices/normal arrays */ for (int j = 0; j < faces.size(); j++) { MyFace myFace = faces.get(j); //Get vertex pointers of face to newVertices in newVertices list int indexP0 = myFace.p0; int indexP1 = myFace.p1; int indexP2 = myFace.p2; //Use pointers as newIndices and fill newIndices array newIndices[j*3] = indexP0; newIndices[j*3+1] = indexP1; newIndices[j*3+2] = indexP2; //Get the vertexdatas out of the list with the pointers VertexData vdP0 = vertexDatas.get(indexP0); VertexData vdP1 = vertexDatas.get(indexP1); VertexData vdP2 = vertexDatas.get(indexP2); //Get the vertex out of the vdata Vertex v0 = vdP0.getVertex(); Vertex v1 = vdP1.getVertex(); Vertex v2 = vdP2.getVertex(); //Put newVertices from vertexdata list in vertex array for the geometry array newVertices[indexP0] = v0; newVertices[indexP1] = v1; newVertices[indexP2] = v2; //Get the faces normal // Vector3D faceNormal = Tools3D.getNormal(v0, v1, v2, true); //myFace.normal;// // Vector3D faceNormal = myFace.normal.getCopy(); // faceNormal.normalizeLocal(); Vector3D faceNormal = myFace.normalNormalized; Vector3D normalP0; if (vdP0.getNeighborFaces().size() > 1){ // logger.debug("Calcing v normal of face: " + myFace.index + " P0"); normalP0 = vdP0.calcVertexNormalAllNeighbors(); }else{ normalP0 = faceNormal; } Vector3D normalP1; if (vdP1.getNeighborFaces().size() > 1){ // logger.debug("Calcing v normal of face: " + myFace.index + " P1"); normalP1 = vdP1.calcVertexNormalAllNeighbors(); }else{ normalP1 = faceNormal; } Vector3D normalP2; if (vdP2.getNeighborFaces().size() > 1){ // logger.debug("Calcing v normal of face: " + myFace.index + " P2"); normalP2 = vdP2.calcVertexNormalAllNeighbors(); }else{ normalP2 = faceNormal; } normals[indexP0] = normalP0; normals[indexP1] = normalP1; normals[indexP2] = normalP2; ////Normalen debug linien sehen mit normals invertiert richtig //aus, aber model wird falsch geshaded! /* normals[indexP0].scale(-1); normals[indexP1].scale(-1); normals[indexP2].scale(-1); */ } //Leider n�tig? da manche models newVertices enthalten, die in den faces garnicht referenziert werden, quasi tote verts for (int j = 0; j < newVertices.length; j++) { if (newVertices[j] == null){ newVertices[j] = nullVect; } //Normals array should be same length as verts, check for nulls here if (normals[j] == null){ normals[j] = nullVect; } //System.out.print(normals[j] + " - "); } logger.debug("----------------------------------------------------------------------------------"); logger.debug("New duplicates of same vertices with different texture coordinates created: " + newDuplicatesWithDiffTexCoordsCreated); logger.debug("New duplicates of same vertices with different normal created: " + newDuplicatesWithDiffNormalCreated); logger.debug("Original number of vertices: " + originalVertices.length); logger.debug("Final number of vertices: " + vertexDatas.size()); logger.debug("Original number of faces: " + originalIndices.length/3); logger.debug("Final number of faces: " + faces.size()); logger.debug("Original number of indices: " + originalIndices.length); logger.debug("Final number of indices: " + newIndices.length); logger.debug("Final number of normals: " + normals.length); logger.debug("----------------------------------------------------------------------------------"); if (newVertices.length > 2 && faces.size() > 0){ // Create a geometryInfo with all vertices of the mesh GeometryInfo geometryInfo = new GeometryInfo(pa, newVertices, newIndices); //Set the normals for the geometry geometryInfo.setNormals(normals, true, false); //Clean up a bit vertexDatas = null; faces = null; return geometryInfo; } return null; } /** * Generates normals for the provided geometry data according to the crease angle. * <br>A crease angle of 180 will result in a all smoothed model, smoothing each vertex normal * across all its neighbor faces' face normals. * <br>A crease angle of zero (0) will result in a flat shaded geometry. Only face normals will * be used then. * <br>A crease angle of 89 will create hard edges at 90 degree angle faces and smooth faces with * less then 90 degree normals. This would be ideal to generate normals for a cube. * <br>The best crease angle for a model has to be found by testing. * * @param pa the pa * @param originalVertices the original vertices * @param originalIndices the original indices * @param originalTexCoords the original tex coords * @param originalTexIndices the original tex indices * @param creaseAngle the crease angle * @param flipTextureY the flip texture y * @param flipTextureX the flip texture x * * @return the geometry info * * indexed, geometry info with normals */ public GeometryInfo generateCreaseAngleNormals(PApplet pa, Vertex[] originalVertices, int[] originalIndices, float[][] originalTexCoords, int[] originalTexIndices, float creaseAngle, boolean flipTextureY, boolean flipTextureX){ int newDuplicatesWithDiffTexCoordsCreated = 0; int newDuplicatesWithDiffNormalCreated = 0; boolean useNormailizedNormalsForAdding = true; logger.debug("-> Loading model with a crease angle: " + creaseAngle); float creaseAngleRad = (float)Math.toRadians(creaseAngle); // float creaseAngleRad = float creaseCosinus = (float)Math.cos(creaseAngle); nullVect = new Vertex(0,0,0, -1, -1); ArrayList<VertexData> vertexDatas = new ArrayList<VertexData>(); ArrayList<MyFace> faces = new ArrayList<MyFace>(); //Init and fill vertexdata list with as many as vertices for (int i = 0; i < originalVertices.length; i++) { //Vertex v = vertices[i]; VertexData vd = new VertexData(); vd.setArrayIndex(i); vertexDatas.add(vd); } ///////////////////////////////////// //GO through indices array and create a face for every three indices (must be triangle array!) int pp0 = 0; int pp1 = 0; int pp2 = 0; int tIndexPP0 = 0; int tIndexPP1 = 0; int tIndexPP2 = 0; for (int i = 0; i < originalIndices.length/3; i++) { //int currentIndex = indices[i]; //Next 3 vertex indices as the new faces pointers for our face pp0 = originalIndices[i*3]; pp1 = originalIndices[i*3+1]; pp2 = originalIndices[i*3+2]; if (originalTexCoords.length > 0){ //Next 3 texture texture indices //(vertex and texture indices indexed in the same order) tIndexPP0 = originalTexIndices[i*3]; tIndexPP1 = originalTexIndices[i*3+1]; tIndexPP2 = originalTexIndices[i*3+2]; } MyFace myFace = new MyFace(); myFace.p0 = pp0; myFace.p1 = pp1; myFace.p2 = pp2; int vertexPointer = pp0; int texturePointer = tIndexPP0; for (int j = 0; j < 3; j++) { if (j == 0) { vertexPointer = pp0; texturePointer = tIndexPP0; } else if(j == 1){ vertexPointer = pp1; texturePointer = tIndexPP1; } else if(j == 2){ vertexPointer = pp2; texturePointer = tIndexPP2; } //Get the vertexdata at the index, the face points to VertexData myVertexData = vertexDatas.get(vertexPointer); Vertex newVertex = new Vertex( originalVertices[vertexPointer].x, originalVertices[vertexPointer].y, originalVertices[vertexPointer].z, originalVertices[vertexPointer].getR(), originalVertices[vertexPointer].getG(), originalVertices[vertexPointer].getB(), originalVertices[vertexPointer].getA() ); //Create texcoords and add to vertex float[] tex = new float[2]; if (originalTexCoords.length > 0){ tex[0] = originalTexCoords[texturePointer][0]; tex[1] = originalTexCoords[texturePointer][1]; if (flipTextureY){ tex[1] = 1.0f-tex[1]; } if (flipTextureX){ tex[0] = 1.0f-tex[0]; } newVertex.setTexCoordU(tex[0]); newVertex.setTexCoordV(tex[1]); } //Check if the vertex data at the pointer is empty thats the case before first time a vertex is added to it here if (myVertexData.getVertex() == null){ myVertexData.setVertex(newVertex); myVertexData.addNeighborFace(myFace); logger.debug("vdP" + j + " vertex in vertexData not initialized -> set it: " + newVertex); }else{//Vertex data at index already contains one or more vertices //Check if the vertex data at the index contains a vertex that is equal to the new vertex and its texture information //-> if true, just add the vertex to that vertexdata and the face it also is in //-> if false, check if a duplicate vertexdata is registered in the vertexdata the face points to // -> if true, add the vertex to the duplicate, change face index to the duplicates index // -> if false, create new vertexdata at end of list, add the vertex and texcoords of the vertex, and register // it as a duplicate in the original vertexdata, change the face index to the new duplicates index if (myVertexData.equalsVertex(newVertex)){ //Register das face mit dem vertex data myVertexData.addNeighborFace(myFace); logger.debug("vdP" + j + "already CONTAINS a vertex with same coords and texture information -> do nothing, just add the current face to its neighborlist"); }else{ //Check if already duplicates were created, maybe with the same vertex and texture information, //then we have to add the vertex to that vertexdata rather than creating a new one int duplicateIndex = myVertexData.getVertDuplicateSameVertDiffTextureCoordListIndex(newVertex); if (duplicateIndex != -1){ //Es gibt schon ein duplicate mit gleichen tex coords wie dieses hier, adde bei duplicateIndex //Change face pointer of p0 to the duplicates index if (j == 0) myFace.p0 = duplicateIndex; else if(j == 1) myFace.p1 = duplicateIndex; else if(j == 2) myFace.p2 = duplicateIndex; //wenn wir orginal hinzuf�gen wird auch allen duplicates neighbor face zugef�gt. myVertexData.addNeighborFace(myFace); logger.debug("vdP" + j + "has different texture coordiantes but a already created duplicate has the same -> change this face pointer to the duplicate one"); }else{//there was no duplicate created with the same texture coords yet -> create one! //Neuen Vertex machen, an original vertices list anh�ngen, neuen index in face auf diesen setzen, face adden VertexData newVertexData = new VertexData(); //Add the vertex information to the newly created vertexdata newVertexData.setVertex(newVertex); //Get new index at end of vertex list int newIndexOfNewVertexData = vertexDatas.size(); //Change the index of the face to the index the new Vdata is created at if (j == 0) myFace.p0 = newIndexOfNewVertexData; else if(j == 1) myFace.p1 = newIndexOfNewVertexData; else if(j == 2) myFace.p2 = newIndexOfNewVertexData; //Tell the vertexdata the index it is in the overall data newVertexData.setArrayIndex(newIndexOfNewVertexData); //Add new vertex data at the end of the list of all vertexdatas vertexDatas.add(newVertexData); //Inform the original vertexdata, that a duplicate with diff tex coords was created and register it myVertexData.registerCreatedDuplicateDiffTexCoords(newVertexData); //Adde face zu orginal face -> damit wird auch duplicates und dem neuen hinzugef�gt, //wenn wir es vorher mit registerDuplicate am orginal registiert haben! myVertexData.addNeighborFace(myFace); //copy the face list the vertex is contained in of the original //to the duplicate (for normal generation later, so they are still neighbors)! newVertexData.addNeighborFaces(myVertexData.getFacesContainedIn()); logger.debug("vdP" + j + "isnt empty but DOESENT CONTAIN (also no duplicate contains) a vertex with same coords and texture information -> creating new V.D. at: " + newIndexOfNewVertexData); newDuplicatesWithDiffTexCoordsCreated++; }//end if duplicate exists }//end if vertexdata already contains Vnew }//end if vertexdata is empty }//end for p0,p1,p2 //Calculate the face's normal vector myFace.calcFaceNormal(vertexDatas); myFace.index = faces.size(); //Add face to facelist faces.add(myFace); } logger.debug("-> Processed duplicate vertex/texture points."); ///////////////////////////////////////// ////////////////////////////////////// if (creaseAngleRad != 0.0) { //Durch alle selbst kreierten faces gehen, und schauen ob ein vertex des faces mit einem seiner //nachbar faces einen "sharp edge" hat oder smooth ist. //wenn smooth -> als smooth neighbor des face pointers hinzuf�gen for (int j = 0; j < faces.size(); j++) { MyFace currentFace = faces.get(j); //Get vertex pointers of face to vertices in vertices list int indexP0 = currentFace.p0; int indexP1 = currentFace.p1; int indexP2 = currentFace.p2; //Get the vertexdatas out of the list with the pointers VertexData vdP0 = vertexDatas.get(indexP0); VertexData vdP1 = vertexDatas.get(indexP1); VertexData vdP2 = vertexDatas.get(indexP2); int[] currentFaceVertIndices = currentFace.getVertexIndices(); //Go through all 3 vertexdata entries in the current face and check for its smooth neighbors for (int faceVD = 0; faceVD < currentFaceVertIndices.length /*currentFace.getVertexIndices().length*/; faceVD++) { VertexData currentFaceVertexData = null; if (faceVD == 0) {currentFaceVertexData = vdP0; /*logger.debug("Face: " + j + " - P0");*/} else if(faceVD == 1) {currentFaceVertexData = vdP1; /*logger.debug("Face: " + j + " - P1");*/} else if(faceVD == 2) {currentFaceVertexData = vdP2; /*logger.debug("Face: " + j + " - P2");*/} ArrayList<MyFace> facesVDIsIn = currentFaceVertexData.getFacesContainedIn(); //Go through all neighbor faces that the vertex(data) is a part of for (int k = 0; k < facesVDIsIn.size(); k++) { MyFace anotherFaceVDisIn = facesVDIsIn.get(k); //Check that we are not considering the same face as the currentFace if (!anotherFaceVDisIn.equals(currentFace)){ boolean onSameSurface = isOnSameSurfaceRadians(currentFace, anotherFaceVDisIn, creaseAngleRad); // boolean onSameSurface = isOnSameSurfaceCosAngle(currentFace, anotherFaceVDisIn, creaseCosinus); //Check if the current face and the other face VD are connected //by an angle < cos_angle degrees, //if so, add the faces to the vds smooth neighbor list (for later normal generation) //if NOT so, create new VertexData, as a copy of the current one at the end of //the vertexdata list, adjust the face pointers of the current face to the new one //(we need another vertex so we have two different normals for them if the two faces arent smoothly connected) if (onSameSurface){ if (faceVD == 0) { logger.debug("Face: " + (currentFace.index) + " (P0:" + currentFace.p0 + " P1:" + currentFace.p1 + " P2:" + currentFace.p2 + ")" + " is smooth with face: " + (anotherFaceVDisIn.index) + " (P0:" + anotherFaceVDisIn.p0 + " P1:" + anotherFaceVDisIn.p1 + " P2:" + anotherFaceVDisIn.p2 + ") at currentFaces' pointer: " + currentFace.p0 + " (" + vdP0.getVertex()+ " )"); } else if(faceVD == 1) { logger.debug("Face: " + (currentFace.index) + " (P0:" + currentFace.p0 + " P1:" + currentFace.p1 + " P2:" + currentFace.p2 + ")" + " is smooth with face: " + (anotherFaceVDisIn.index) + " (P0:" + anotherFaceVDisIn.p0 + " P1:" + anotherFaceVDisIn.p1 + " P2:" + anotherFaceVDisIn.p2 + ") at currentFaces' pointer: " + currentFace.p1+ " (" + vdP1.getVertex()+ " )"); } else if(faceVD == 2) { logger.debug("Face: " + (currentFace.index) + " (P0:" + currentFace.p0 + " P1:" + currentFace.p1 + " P2:" + currentFace.p2 + ")" + " is smooth with face: " + (anotherFaceVDisIn.index) + " (P0:" + anotherFaceVDisIn.p0 + " P1:" + anotherFaceVDisIn.p1 + " P2:" + anotherFaceVDisIn.p2 + ") at currentFaces' pointer: " + currentFace.p2+ " (" + vdP2.getVertex()+ " )"); } if (faceVD == 0) { currentFace.addSmoothNeighborP0(anotherFaceVDisIn); } else if(faceVD == 1) { currentFace.addSmoothNeighborP1(anotherFaceVDisIn); } else if(faceVD == 2) { currentFace.addSmoothNeighborP2(anotherFaceVDisIn); } }//if smooth }//if not checking against this same face }//for loop through all faces the current vertex is in }//for loop through all 3 vertexdatas of the current face }//for loop through all previously created faces }//end if creaseAngle != 0.0 /////////////////////////////////////////////////////////////////////////// ///////////////// //moved to the next loop // //Vertex normalen berechnen // for (int j = 0; j < faces.size(); j++) { // MyFace currentFace = faces.get(j); // currentFace.calcVertexNormals(useNormailizedNormalsForAdding); // } ////////////////// // /|\ // combine? // \|/ ////////////////////////////////////////////////////////////////////// //Die vertex normalen wurden pro face und pro pointer auf ein vertex in den faces berechnet //Jetzt f�gen wir den VertexDatas, die die vertices representieren diese vertex normalen hinzu. //Wenn in mehreren faces auf den gleichen vertex gezeigt wird, aber in dem face f�r diesen vertex eine //andere normale berechnet wurde (weil face mit anderen smooth ist) m�ssen wir evtl das Vertex(data) duplizieren //und diesem die andere normale hinzuf�gen for (int j = 0; j < faces.size(); j++) { MyFace currentFace = faces.get(j); //GENERATE THE FACES VERTEX NORMAL BASED ON ITS SMOOTH NEIGHBORS currentFace.calcVertexNormals(useNormailizedNormalsForAdding); int[] faceVertexPointer = currentFace.getVertexIndices(); //Go trough all (3) vertexpointer p0..2 of the current face for (int i = 0; i < faceVertexPointer.length; i++) { int currentVertexPointer = faceVertexPointer[i]; logger.debug("-> Processing face[" + j + "].P" + i + " Vertex: " + vertexDatas.get(currentVertexPointer).getVertex()); //Hole vertexdata auf das das currentFace an dem aktuellen zeiger (p0,p1,p2) zeigt VertexData currentVertexDataP0OrP1OrP2 = vertexDatas.get(currentVertexPointer); //Get normal saved in the vertexdata at position Face.Px Vector3D currentFacesCurrentVDNormal = currentVertexDataP0OrP1OrP2.getUniqueVertexNormal(); //Get vertex normal array calculated and saved in the face for each point Px Vector3D[] vertexNormalsCurrentFace = currentFace.getVertexNormals(); //Check if the vertexnormal data at the pointer is null -> thats the case before first time a vertexnormal is set here if (currentFacesCurrentVDNormal == null){ currentVertexDataP0OrP1OrP2.setUniqueVertexNormal(vertexNormalsCurrentFace[i]); logger.debug("Face " + j + ", vdP" + i + " (Vertex: " + vertexDatas.get(currentVertexPointer).getVertex() + ")" + " normal not yet set -> set it: " + vertexNormalsCurrentFace[i]); }else{//Vertexdata at index already contains a vertexnormal -> check if its the equal to this faces currentVD's if (currentFacesCurrentVDNormal.equalsVectorWithTolerance(vertexNormalsCurrentFace[i], ToolsMath.ZERO_TOLERANCE)){ logger.debug("Face " + j + ", vdP" + i + " (Vertex: " + vertexDatas.get(currentVertexPointer).getVertex() + ")" + " already CONTAINS a normal with the same values as the normal of this faces point -> we can leave the index and normal at the same place: N:" + vertexNormalsCurrentFace[i]); }else{ int duplicateIndexOfSameVertDiffNormal = currentVertexDataP0OrP1OrP2.getVertDuplicateSameVertDiffNormalListIndex(vertexNormalsCurrentFace[i]); if (duplicateIndexOfSameVertDiffNormal != -1){ //Es gibt schon ein duplicate mit gleichen tex coords wie dieses hier, adde bei duplicateIndex //Change face pointer of p0 to the duplicates index if (i == 0) currentFace.p0 = duplicateIndexOfSameVertDiffNormal; else if(i == 1) currentFace.p1 = duplicateIndexOfSameVertDiffNormal; else if(i == 2) currentFace.p2 = duplicateIndexOfSameVertDiffNormal; logger.debug("Face " + j + " vdP" + i + " (Vertex: " + vertexDatas.get(currentVertexPointer).getVertex() + ")" + " vertexnormal is conform with a duplicate of the original vertex -> point to that duplicate: N:" + vertexNormalsCurrentFace[i]); }else{//duplicate index == -1 -> neither the orignal faces point has the same vertexnormal nor a duplicate with different normal has that normal -> create new VD with the different normal and same vertex //Neuen Vertex machen, an original vertices list anh�ngen, neuen index in face auf diesen setzen VertexData newVertexData = new VertexData(); //Add the vertex information to the newly created vertexdata newVertexData.setVertex(currentVertexDataP0OrP1OrP2.getVertex()); //Set the vertex normal for the new vertexdata as the vertex normal of the current face' calced normal for that vertex newVertexData.setUniqueVertexNormal(vertexNormalsCurrentFace[i]); //Get new index at end of vertex list int newIndexOfNewVertexData = vertexDatas.size(); //Change the index of the face to the index the new Vdata is created at if (i == 0) currentFace.p0 = newIndexOfNewVertexData; else if(i == 1) currentFace.p1 = newIndexOfNewVertexData; else if(i == 2) currentFace.p2 = newIndexOfNewVertexData; //Tell the vertexdata the index it is in the overall data newVertexData.setArrayIndex(newIndexOfNewVertexData); //Add new vertex data at the end of the list of all vertexdatas vertexDatas.add(newVertexData); //Inform the original vertexdata, that a duplicate with diff tex coords was created and register it currentVertexDataP0OrP1OrP2.registerCreatedDuplicateDiffNormal(newVertexData); logger.debug("Face " + j + ", vdP" + i + " (Vertex: " + vertexDatas.get(currentVertexPointer).getVertex() + ")" + " has a different vertexnormal and DOESENT CONTAIN a link to a duplicate vertex with same normal information -> creating new VD at: " + newIndexOfNewVertexData + " N:" + vertexNormalsCurrentFace[i]); newDuplicatesWithDiffNormalCreated++; }//end if duplicate exists }//end if vertexdata already contains Vnew }//end if vertexdata is empty } } //////////////////////////////////// ////////////////////////////////////////// //Create arrays Vertex[] newVertices = new Vertex[vertexDatas.size()]; Vector3D[] normals = new Vector3D[vertexDatas.size()]; int[] newIndices = new int[faces.size()*3]; /* * Go through the final faces list and fill vertex/newIndices/normal arrays */ for (int j = 0; j < faces.size(); j++) { MyFace myFace = faces.get(j); //Get vertex pointers of face to newVertices in newVertices list int indexP0 = myFace.p0; int indexP1 = myFace.p1; int indexP2 = myFace.p2; //Use pointers as newIndices and fill newIndices array newIndices[j*3] = indexP0; newIndices[j*3+1] = indexP1; newIndices[j*3+2] = indexP2; //Get the vertexdatas out of the list with the pointers VertexData vdP0 = vertexDatas.get(indexP0); VertexData vdP1 = vertexDatas.get(indexP1); VertexData vdP2 = vertexDatas.get(indexP2); //Get the vertex out of the vdata Vertex v0 = vdP0.getVertex(); Vertex v1 = vdP1.getVertex(); Vertex v2 = vdP2.getVertex(); //Put newVertices from vertexdata list in vertex array for the geometry array newVertices[indexP0] = v0; newVertices[indexP1] = v1; newVertices[indexP2] = v2; //Get the faces normal normals[indexP0] = vdP0.uniqueVertexNormal; normals[indexP1] = vdP1.uniqueVertexNormal; normals[indexP2] = vdP2.uniqueVertexNormal; ////Normalen debug linien sehen mit normals invertiert richtig //aus, aber model wird falsch geshaded! /* normals[indexP0].scale(-1); normals[indexP1].scale(-1); normals[indexP2].scale(-1); */ } //Leider n�tig? da manche models newVertices enthalten, die in den faces garnicht referenziert werden, quasi tote verts for (int j = 0; j < newVertices.length; j++) { if (newVertices[j] == null){ newVertices[j] = nullVect; } //Normals array should be same length as verts, check for nulls here if (normals[j] == null){ normals[j] = nullVect; } // System.out.print(normals[j] + " - "); } // logger.debug(); /////////////////////////////////////////////////////// logger.debug("----------------------------------------------------------------------------------"); logger.debug("New duplicates of vertices with same vertex but different texture coordinates created: " + newDuplicatesWithDiffTexCoordsCreated); logger.debug("New duplicates of vertices with same vertex but different normal created: " + newDuplicatesWithDiffNormalCreated); logger.debug("Original number of vertices: " + originalVertices.length); logger.debug("Final number of vertices: " + vertexDatas.size()); logger.debug("Final number of indices: " + newIndices.length); logger.debug("Final number of faces: " + faces.size()); logger.debug("Final number of normals: " + normals.length); logger.debug("----------------------------------------------------------------------------------"); if (newVertices.length > 2 && faces.size() > 0){ // Create a geometryInfo with all vertices of the mesh GeometryInfo geometryInfo = new GeometryInfo(pa, newVertices, newIndices); //Set the normals for the geometry geometryInfo.setNormals(normals, true, false); //Clean up a bit vertexDatas = null; faces = null; return geometryInfo; } return null; } /** * Sets the debug mode. * * @param debug the debug */ public void setDebug(boolean debug) { if (debug) logger.setLevel(Level.DEBUG); else logger.setLevel(Level.ERROR); } /** * This influences the normal generation with crease angles. * If <code>useNormalsEqualToFace</code> is set to true, normals * of neighbor faces, that have the same normal as the face and vertex were checking * against will be used in the calculation. * </br>If <code>useNormalsEqualToFace</code> is set to false, these normals equal to * the test face normal will be discarded to avoid adding up redundant normals. * </br>The default is FALSE. * * @param useNormalsEqualToFace use other normals equal to face or */ public void setUseNormalsEqualToFace(boolean useNormalsEqualToFace) { this.useNormalsEqualToFace = useNormalsEqualToFace; } /** * This influences the normal generation with crease angles. * <br<>If <code>useEqualNeighborNormalsAgain</code> is set to true, normals * of neighbor faces, that have the same normal as a neighbor face previously used in * the calculation for one same vertex normal will again be used and added in. * <br>If <code>useEqualNeighborNormalsAgain</code> is set to false, these normals will * not be added in the calculation again. * <br>The default is FALSE. * * @param useEqualNeighborNormalsAgain use equal neighbor normals again */ public void setUseEqualNeighborNormalsAgain(boolean useEqualNeighborNormalsAgain) { this.useEqualNeighborNormalsAgain = useEqualNeighborNormalsAgain; } /** * Calculates .. * * @param face1 the face1 * @param face2 the face2 * @param cosAngle the cos angle * * @return true, if checks if is on same surface radians */ private boolean isOnSameSurfaceRadians(MyFace face1, MyFace face2, float cosAngle) { // float cosineBetweenNormals = face1.normal.dot(face2.normal); // logger.debug(Math.acos(cosineBetweenNormals)); // boolean smooth = cosineBetweenNormals > cosAngle; // //boolean smoothTriangles = (cosineBetweenNormals > cosAngle); // //logger.debug("surface: compare dot=" +dot + " cos-angle=" + cos_angle + " return " + (dot > cos_angle)); // logger.debug(Vector3D.angleBetween(face1.normal, face2.normal)); // logger.debug(); // // return smooth; boolean debugSmoothChecking = false; float angleBetweenNorms = Vector3D.angleBetween(face1.normal, face2.normal); if (debugSmoothChecking){ float angleBetweenNormsDegrees = (float)Math.toDegrees(angleBetweenNorms); logger.debug("Angle between normals :" + angleBetweenNormsDegrees); logger.debug("Crease angle: " + Math.toDegrees(cosAngle)); } boolean smooth = angleBetweenNorms < cosAngle; if (debugSmoothChecking) logger.debug("-> Smooth: " + smooth); if (Float.isNaN(angleBetweenNorms)){ smooth = true; if (debugSmoothChecking) logger.debug("NAN!"); } return smooth; // float threshold = (float)Math.cos(creaseAngle); // float cosine = face1.normal.dot(face2.normal); // boolean smooth = cosine > threshold; // // //boolean smoothTriangles = (cosine > threshold); // //logger.debug("surface: compare dot=" +dot + " cos-angle=" + cos_angle + " return " + (dot > cos_angle)); // return smooth; // return false; //// return true; } /** * Calculates .. * * @param face1 the face1 * @param face2 the face2 * @param cosAngle the cos angle * * @return true, if checks if is on same surface cos angle */ private boolean isOnSameSurfaceCosAngle(MyFace face1, MyFace face2, float cosAngle) { //FIXME really correct? float cosineBetweenNormals = face1.normal.dot(face2.normal); boolean smooth = cosineBetweenNormals > (float)Math.abs(cosAngle); if (Float.isNaN(cosineBetweenNormals)) smooth = true; return smooth; } /** * Class representing a triangle face of a mesh. * * @author C.Ruff */ private class MyFace{ /** The p0. */ int p0; /** The p1. */ int p1; /** The p2. */ int p2; /** The smooth neighbors p0. */ private ArrayList<MyFace> smoothNeighborsP0; /** The smooth neighbors p1. */ private ArrayList<MyFace> smoothNeighborsP1; /** The smooth neighbors p2. */ private ArrayList<MyFace> smoothNeighborsP2; /** The normal. */ Vector3D normal; /** The normal normalized. */ Vector3D normalNormalized; /** The center. */ private Vector3D center; /** The index. */ int index; /** The vertex normal p0. */ private Vector3D vertexNormalP0; /** The vertex normal p1. */ private Vector3D vertexNormalP1; /** The vertex normal p2. */ private Vector3D vertexNormalP2; /** The vertex normals. */ private Vector3D[] vertexNormals; /** * Instantiates a new my face. */ public MyFace(){ p0 = -1; p1 = -1; p2 = -1; //normal = nullVect; smoothNeighborsP0 = new ArrayList<MyFace>(); smoothNeighborsP1 = new ArrayList<MyFace>(); smoothNeighborsP2 = new ArrayList<MyFace>(); } /** * Gets the vertex indices. * * @return the vertex indices * * the pointers (indices) into the vertex array, this face holds (length=3 here) */ public int[] getVertexIndices() { return new int[]{p0,p1,p2}; } /** * Registers the face as a smooth neighbor of this faces vertex at * the index P0. This is only relevant if a crease angle is used. * * @param neighborFace the neighbor face */ public void addSmoothNeighborP0(MyFace neighborFace){ if (!smoothNeighborsP0.contains(neighborFace)){ smoothNeighborsP0.add(neighborFace); } } /** * Registers the face as a smooth neighbor of this faces vertex at * the index P1. This is only relevant if a crease angle is used. * * @param neighborFace the neighbor face */ public void addSmoothNeighborP1(MyFace neighborFace){ if (!smoothNeighborsP1.contains(neighborFace)){ smoothNeighborsP1.add(neighborFace); } } /** * Registers the face as a smooth neighbor of this faces vertex at * the index P2. This is only relevant if a crease angle is used. * * @param neighborFace the neighbor face */ public void addSmoothNeighborP2(MyFace neighborFace){ if (!smoothNeighborsP2.contains(neighborFace)){ smoothNeighborsP2.add(neighborFace); } } /** * Calculates this face's face normal. * * @param vertexList the vertex list */ public void calcFaceNormal(ArrayList<VertexData> vertexList){ //We DONT NORMALIZE YET! this.normal = ToolsGeometry.getNormal(vertexList.get(p0).getVertex(), vertexList.get(p1).getVertex(), vertexList.get(p2).getVertex(), false); this.normalNormalized = normal.getCopy(); this.normalNormalized.normalizeLocal(); } /** * Gets the center point local. * * @param vertexDataList the vertex data list * * @return the center point local * * the center point of this face */ public Vector3D getCenterPointLocal(ArrayList<VertexData> vertexDataList){ center = vertexDataList.get(p0).getVertex().getCopy(); center.addLocal(vertexDataList.get(p1).getVertex()); center.addLocal(vertexDataList.get(p2).getVertex()); center.scaleLocal(ToolsMath.ONE_THIRD); return this.center; } /** * Cals the vertex normals for the three points this face has. * 2 different ways are implemented from which can be chosen by * the boolean variable. * if "useNormailizedNormalsForAdding" is false, all unnormalized normals of all * smooth neighbor faces are added up and at the end normalized. * If "useNormailizedNormalsForAdding" is false, all neighbors face normals are normalized before adding up. * Also, normals that are equal to this faces normal arent added again. * Furthermore, only normals that arent equal to one already added from another * neighbor are added in. This is slower but should produce better results. * * @param useNormailizedNormalsForAdding the use normailized normals for adding */ public void calcVertexNormals(boolean useNormailizedNormalsForAdding) { if (normal.equals(nullVect)){ throw new RuntimeException("We have to calculate the face normal before calling calcVertexNormals!"); } if (useNormailizedNormalsForAdding){ //Use normalized normals for all calculations //Also checks if normals are attempted to be added that are equal to this faces normal //or normals that have already been added -> those are not added // /* Vector3D normalizedFaceNormal = this.normal.getCopy(); normalizedFaceNormal.normalizeLocal(); logger.debug(""); logger.debug("Face " + index + " normal: " + this.normal + " Normalized: " + normalizedFaceNormal); logger.debug("P0:"); //For each face point, calc vertex normal by adding up all //smooth connected neighbors + this faces' normal and normalize in the end ArrayList<Vector3D> alreadyAddedInP0 = new ArrayList<Vector3D>(); // vertexNormalP0 = this.normal.getCopy(); //Init with own faces face normal vertexNormalP0 = normalizedFaceNormal.getCopy(); for (int i = 0; i < smoothNeighborsP0.size(); i++) { MyFace neighborFaceP0 = smoothNeighborsP0.get(i); Vector3D nextSmoothNeighborNormal = neighborFaceP0.normal; Vector3D nextSmoothNeighborNormalNormalized = nextSmoothNeighborNormal.getCopy(); //TODO doch nochmal probieren mit vorher ausgerechneter normal? sonst performance loss! nextSmoothNeighborNormalNormalized.normalizeLocal();//neighborFaceP0.normalNormalized; boolean alreadyAddedSameNormalIn = false; //Dont add faces normals that are equal to this faces normals if (!useNormalsEqualToFace && nextSmoothNeighborNormalNormalized.equalsVectorWithTolerance(normalizedFaceNormal, ToolsMath.ZERO_TOLERANCE)){ alreadyAddedSameNormalIn = true; logger.debug("Not using normal: " + nextSmoothNeighborNormalNormalized + " of face " + neighborFaceP0.index + " in vertex norm calc because its equal to this faces normal."); } // else //Dont add face normals that are equal to one already added // { if (!useEqualNeighborNormalsAgain){ for(Vector3D neighBorNorm : alreadyAddedInP0){ if(neighBorNorm.equalsVectorWithTolerance(nextSmoothNeighborNormalNormalized, ToolsMath.ZERO_TOLERANCE)){ alreadyAddedSameNormalIn = true; logger.debug("Already added same normal -> dont add again N: " + neighBorNorm); } } } // } if (!alreadyAddedSameNormalIn){ vertexNormalP0.addLocal(nextSmoothNeighborNormalNormalized); alreadyAddedInP0.add(nextSmoothNeighborNormalNormalized); logger.debug("Added normal: " + nextSmoothNeighborNormalNormalized + " of face: " + neighborFaceP0.index); } } vertexNormalP0.normalizeLocal(); logger.debug("P1"); //For each face point, calc vertex normalby adding up all //smooth connected neighbors + this faces' normal and normalize in the end ArrayList<Vector3D> alreadyAddedInP1 = new ArrayList<Vector3D>(); // vertexNormalP1 = this.normal.getCopy(); //Init with own faces face normal vertexNormalP1 = normalizedFaceNormal.getCopy(); for (int i = 0; i < smoothNeighborsP1.size(); i++) { MyFace neighborFaceP1 = smoothNeighborsP1.get(i); Vector3D nextSmoothNeighborNormal = neighborFaceP1.normal; Vector3D nextSmoothNeighborNormalNormalized = nextSmoothNeighborNormal.getCopy(); //TODO doch nochmal probieren mit vorher ausgerechneter normal? sonst performance loss! nextSmoothNeighborNormalNormalized.normalizeLocal();//neighborFaceP1.normalNormalized; boolean alreadyAddedSameNormalIn = false; //Dont add faces normals that are equal to this faces normals if (!useNormalsEqualToFace && nextSmoothNeighborNormalNormalized.equalsVectorWithTolerance(normalizedFaceNormal, ToolsMath.ZERO_TOLERANCE)){ alreadyAddedSameNormalIn = true; logger.debug("Not using normal: " + nextSmoothNeighborNormalNormalized + " of face " + neighborFaceP1.index + " in vertex norm calc because its equal to this faces normal."); } // else //Dont add face normals that are equal to one already added // { if (!useEqualNeighborNormalsAgain){ for(Vector3D neighBorNorm : alreadyAddedInP1){ if(neighBorNorm.equalsVectorWithTolerance(nextSmoothNeighborNormalNormalized, ToolsMath.ZERO_TOLERANCE)){ alreadyAddedSameNormalIn = true; logger.debug("Already added same normal -> dont add again N: " + neighBorNorm); } } } // } if (!alreadyAddedSameNormalIn){ vertexNormalP1.addLocal(nextSmoothNeighborNormalNormalized); alreadyAddedInP1.add(nextSmoothNeighborNormalNormalized); logger.debug("Added normal: " + nextSmoothNeighborNormalNormalized + " of face: " + neighborFaceP1.index); } } vertexNormalP1.normalizeLocal(); logger.debug("P2"); //For each face point, calc vertex normalby adding up all //smooth connected neighbors + this faces' normal and normalize in the end ArrayList<Vector3D> alreadyAddedInP2 = new ArrayList<Vector3D>(); // vertexNormalP2 = this.normal.getCopy(); //Init with own faces face normal vertexNormalP2 = normalizedFaceNormal.getCopy(); for (int i = 0; i < smoothNeighborsP2.size(); i++) { MyFace neighborFaceP2 = smoothNeighborsP2.get(i); Vector3D nextSmoothNeighborNormal = neighborFaceP2.normal; Vector3D nextSmoothNeighborNormalNormalized = nextSmoothNeighborNormal.getCopy(); //TODO doch nochmal probieren mit vorher ausgerechneter normal? sonst performance loss! nextSmoothNeighborNormalNormalized.normalizeLocal();//neighborFaceP2.normalNormalized; boolean alreadyAddedSameNormalIn = false; //Dont add faces normals that are equal to this faces normals if (!useNormalsEqualToFace && nextSmoothNeighborNormalNormalized.equalsVectorWithTolerance(normalizedFaceNormal, ToolsMath.ZERO_TOLERANCE)){ alreadyAddedSameNormalIn = true; logger.debug("Not using normal: " + nextSmoothNeighborNormalNormalized + " of face " + neighborFaceP2.index + " in vertex norm calc because its equal to this faces normal."); } // else //Dont add face normals that are equal to one already added // { if (!useEqualNeighborNormalsAgain){ for(Vector3D neighBorNorm : alreadyAddedInP2){ if(neighBorNorm.equalsVectorWithTolerance(nextSmoothNeighborNormalNormalized, ToolsMath.ZERO_TOLERANCE)){ alreadyAddedSameNormalIn = true; logger.debug("Already added same normal -> dont add again N: " + neighBorNorm); } } } // } if (!alreadyAddedSameNormalIn){ vertexNormalP2.addLocal(nextSmoothNeighborNormalNormalized); alreadyAddedInP2.add(nextSmoothNeighborNormalNormalized); logger.debug("Added normal: " + nextSmoothNeighborNormalNormalized + " of face: " + neighborFaceP2.index); } } vertexNormalP2.normalizeLocal(); }else{ //Just add up all smooth neighbors and normalize after //P0 Normal vertexNormalP0 = this.normal.getCopy(); for (int i = 0; i < smoothNeighborsP0.size(); i++) { MyFace neighborFaceP0 = smoothNeighborsP0.get(i); vertexNormalP0.addLocal(neighborFaceP0.normal); } vertexNormalP0.normalizeLocal(); //P1 Normal vertexNormalP1 = this.normal.getCopy(); for (int i = 0; i < smoothNeighborsP1.size(); i++) { MyFace neighborFaceP1 = smoothNeighborsP1.get(i); vertexNormalP1.addLocal(neighborFaceP1.normal); } vertexNormalP1.normalizeLocal(); vertexNormalP2 = this.normal.getCopy(); for (int i = 0; i < smoothNeighborsP2.size(); i++) { MyFace neighborFaceP2 = smoothNeighborsP2.get(i); vertexNormalP2.addLocal(neighborFaceP2.normal); } vertexNormalP2.normalizeLocal(); } logger.debug("Face: " + index + " -> P0 VertexNormal:-> " + vertexNormalP0); logger.debug("Face: " + index + " -> P1 VertexNormal:-> " + vertexNormalP1); logger.debug("Face: " + index + " -> P2 VertexNormal:-> " + vertexNormalP2); logger.debug(""); vertexNormals = new Vector3D[]{vertexNormalP0,vertexNormalP1,vertexNormalP2}; } /** * Gets the vertex normals. * * @return the vertex normals */ public Vector3D[] getVertexNormals() { if (vertexNormals == null){ throw new RuntimeException("We have to calculate the vertex normals first!"); } return vertexNormals; } }//End MyFace class /** * Class to hold information about a mesh vertex. * * @author C.Ruff */ private class VertexData{ /** The array index. */ private int arrayIndex; /** The vertex. */ private Vertex vertex; /** The faces. */ private ArrayList<MyFace> faces; /** The duplications with diff tex coords. */ private ArrayList<VertexData> duplicationsWithDiffTexCoords; // private ArrayList<MyFace> smoothNeighbors; // private Vector3D vertexNormal; // private boolean vertexNormalDirty; /** The unique vertex normal. */ private Vector3D uniqueVertexNormal; /** The duplications with diff normal. */ private ArrayList<VertexData> duplicationsWithDiffNormal; //Normal calculated in the getSmoothnormals method /** The all neighbors normal. */ private Vector3D allNeighborsNormal; /** * Constructor. */ public VertexData() { super(); faces = new ArrayList<MyFace>(); duplicationsWithDiffTexCoords = new ArrayList<VertexData>(); duplicationsWithDiffNormal = new ArrayList<VertexData>(); // smoothNeighbors = new ArrayList<MyFace>(); arrayIndex = -1; allNeighborsNormal = null; // vertexNormalDirty = true; // vertexNormal = new Vector3D(0,0,0); } /** * Adds and registers the vertex with this vertexdata. The Vertex * datas vertex is null at the start and has to be initilazied once here. * * @param vertex the vertex */ public void setVertex(Vertex vertex){ this.vertex = vertex; } /** * Gets the vertex. * * @return the vertex * * Returns the vertex this vertex data wraps around. */ public Vertex getVertex() { return vertex; } //this is wrong here, the vertex normal when considering a crease angle //has to be calculated per face and per face vertex instead of per vertex here //alone. Because for one face the same vertex has to be smoothed with different //faces than for another face. // /** // * Tells the VertexData, that the specified face is a neighbor which should // * be used to calculate a smooth vertex normal later. // * // * @param myFace // */ // public void addNewSmoothNeighbor(MyFace myFace){ // if (! this.smoothNeighbors.contains(myFace)){ // this.smoothNeighbors.add(myFace); // this.vertexNormalDirty = true; // } // } // /** // * Calculates and returns the vertexnormal for this vertexdata, // * using all the vertexes smooth neighbors and adding them up, then normalizing. // * This should be called only after all the smooth neighbors were added. // * // * @return // * the normal // */ // public Vector3D calcVertexNormalSmoothNeighbors() { // if (vertexNormalDirty){ // vertexNormal = new Vector3D(0,0,0); // // //Add up face normals of smooth neighbors // for (int i = 0; i < smoothNeighbors.size(); i++) { // MyFace smoothNeighbor = smoothNeighbors.get(i); // vertexNormal.addLocal(smoothNeighbor.normal); // } // //Normalize in the end // vertexNormal.normalizeLocal(); // vertexNormalDirty = false; // return vertexNormal; // }else{ // return vertexNormal; // } // } // /** // * @return // * whether the vertexdata has smooth neighbors // */ // public boolean hasSmoothNeighborFaces(){ // return !this.smoothNeighbors.isEmpty(); // } /** * Add and register the specified neighbor for this vertexdata. * This also adds the face to the duplicates registered (added) with this vertexdata * with different tex coords but same vertex pointer. * * @param face the face */ public void addNeighborFace(MyFace face){ if (!faces.contains(face)) faces.add(face); //Auch duplicates face hinzuf�gen, da sie ja den gleichen vertex sharen //und f�r die normalenberechnung alle neighborfaces brauchen for (VertexData vdDuplicate : this.getDuplicateVertexWithDiffTexCoordsList()){ vdDuplicate.addNeighborFace(face); } } /** * Add and register the specified neighbors for this vertexdata and ints duplicates * with different tex coords but same vertex pointer. * * @param addFaces the add faces */ public void addNeighborFaces(ArrayList<MyFace> addFaces){ for (MyFace currFace: addFaces){ if (!faces.contains(currFace)){ faces.add(currFace); } } //Auch duplicates faces hinzuf�gen, da sie ja den gleichen vertex sharen //und f�r die normalenberechnung alle neighborfaces brauchen for (VertexData vdDuplicate : this.getDuplicateVertexWithDiffTexCoordsList()){ vdDuplicate.addNeighborFaces(addFaces); } } /** * Gets the neighbor faces. * * @return the neighbor faces * * All neighbors of this vertex, so all faces this vertex is a part of */ public ArrayList<MyFace> getNeighborFaces(){ return this.faces; } /** * Calculates and returns the vertex normal considering * _all_ the vertexes neighbors (smooth or not!). Results * in an all smoothed model. * * @return the vector3 d * * the resulting vertex normal */ public Vector3D calcVertexNormalAllNeighbors(){ if (allNeighborsNormal == null ){ //Add up face normals of smooth neighbors Vector3D allNeighborNormal = new Vector3D(0,0,0); for (int i = 0; i < faces.size(); i++) { MyFace neighbor = faces.get(i); allNeighborNormal.addLocal(neighbor.normal); logger.debug("Vertex index:" + this.getArrayIndex() + " calcing in neighbor normal of face: " + neighbor.index); } //Normalize in the end allNeighborNormal.normalizeLocal(); allNeighborsNormal = allNeighborNormal; return allNeighborsNormal; }else{ return allNeighborsNormal; } } ///////////// Handle vertices with same vertex but different texture coordinates /** * Returns the index of the already created duplicate with the same vertex and texture * information we are checking with, or "-1" if no duplicate with that information exists. * Then we have to create a new duplicate and register it to this Vertex Data's duplicate list. * * @param vertex the vertex * * @return the vert duplicate same vert diff texture coord list index * * the index of the dupicate with the same tex coords or -1 if there is none */ public int getVertDuplicateSameVertDiffTextureCoordListIndex(Vertex vertex){ //Go through list of all duplicates for (int i = 0; i < duplicationsWithDiffTexCoords.size(); i++) { VertexData possibleDuplicate = duplicationsWithDiffTexCoords.get(i); if (possibleDuplicate.equalsVertex(vertex)){ return possibleDuplicate.getArrayIndex(); } } return -1; } /** * Checks if the vertex with the same coordinates and texture coordinates was already added * to this vertexData. * * @param vertex the vertex * * @return true, if equals vertex */ private boolean equalsVertex(Vertex vertex){ return this.vertex.equalsVector(vertex); }//contains() /** * Tell the vertexdata obj, that we have created a duplicate and saved it. * * @param vd the vd */ public void registerCreatedDuplicateDiffTexCoords(VertexData vd){ this.duplicationsWithDiffTexCoords.add(vd); } /** * Gets the duplicate vertex with diff tex coords list. * * @return the duplicate vertex with diff tex coords list */ private ArrayList<VertexData> getDuplicateVertexWithDiffTexCoordsList(){ return this.duplicationsWithDiffTexCoords; } ///////////// Handle vertices with same vertex but different texture coordinates ///////////// Handle vertices with same vertex but different Vertex normal // This is only relevant if a crease angle is used! /** * Returns the vertex normal that has been calculated and set * with a crease angle for a group of faces for which this normal * is the same. * * @return the unique vertex normal */ public Vector3D getUniqueVertexNormal(){ return uniqueVertexNormal; } /** * Sets the vertex normal for this vertex(data). In the crease angle * calculation process. May only set it once. Different normals belonging * to the same vertex have to be put in another vertex data object! * The vertex normal is calculated in the faces and then set here. * * @param newVertexNormal the new vertex normal */ public void setUniqueVertexNormal(Vector3D newVertexNormal) { this.uniqueVertexNormal = newVertexNormal; } /** * The crease angle cal process tell this vertex data that it created another vertex data * because in this one, the vertex is the same but the normal differs -> duplicate the vertex!. * * @param vdWithDiffNormal the vd with diff normal */ public void registerCreatedDuplicateDiffNormal(VertexData vdWithDiffNormal) { this.duplicationsWithDiffNormal.add(vdWithDiffNormal); } /** * Asks the vertex data if a duplicate of itself with a different normal was created and registered as a duplicate. * <br>If there is a duplicate already registered with the same normal to be checked with, we get the index of that duplicate * in the overall vertex data list and can point the face to the duplicate so we dont have to create another vertexdata. * <br>It is assumed that the normal we are checking against belongs to a vertex with the same values as this Vertex datas vertex! * * @param normalToCheckWith the normal to check with * * @return the vert duplicate same vert diff normal list index * * the index of the duplicate or -1 if there is none */ public int getVertDuplicateSameVertDiffNormalListIndex(Vector3D normalToCheckWith) { // Go through list of all duplicates for (int i = 0; i < duplicationsWithDiffNormal.size(); i++) { VertexData possibleDuplicate = duplicationsWithDiffNormal.get(i); if (possibleDuplicate.getUniqueVertexNormal().equalsVectorWithTolerance(normalToCheckWith, ToolsMath.ZERO_TOLERANCE)){ return possibleDuplicate.getArrayIndex(); } } return -1; } ///////////// Handle vertices with same vertex but different Vertex normal /** * Tell the vertexdata which index its at in a list of vertexdatas for example. * * @param i the i */ public void setArrayIndex(int i){ this.arrayIndex = i; } /** * Gets the array index. * * @return the array index * * returns the index, the vertexData is at in the overall vertex data list. * ->This has to be set manually by calling <code>setArrayIndex</code> first */ public int getArrayIndex(){ return this.arrayIndex; } /** * Gets the faces contained in. * * @return the faces contained in * * a list of faces, this vertex(-data) is a part of (all its neighbors) * NOTE: not at all stages of the algorithm returns all real neighbors. Some may be deleted already. */ public ArrayList<MyFace> getFacesContainedIn(){ return this.faces; } }//Vertexdata class /*----------------------------------------------------------------------------*/ }