diff --git a/MeshModel/src/main/java/cz/fidentis/analyst/mesh/core/MeshFacet.java b/MeshModel/src/main/java/cz/fidentis/analyst/mesh/core/MeshFacet.java index 438af33a71d319bd2fdfd9f751bec35bffc09875..74328019275457694749933698830ba5fe6055aa 100644 --- a/MeshModel/src/main/java/cz/fidentis/analyst/mesh/core/MeshFacet.java +++ b/MeshModel/src/main/java/cz/fidentis/analyst/mesh/core/MeshFacet.java @@ -9,7 +9,7 @@ import java.util.List; * @author Matej Lukes */ public class MeshFacet { - private List<MeshPoint> vertexes = new ArrayList<>(); + private List<MeshPoint> vertices = new ArrayList<>(); private CornerTable cornerTable = new CornerTable(); /** @@ -18,7 +18,7 @@ public class MeshFacet { * @return vertex */ public MeshPoint getVertex(int index) { - return vertexes.get(index); + return vertices.get(index); } /** @@ -26,15 +26,23 @@ public class MeshFacet { * @param point new vertex */ public void addVertex(MeshPoint point){ - vertexes.add(point); + vertices.add(point); } /** - * returns number of vertexes in MeshFacet - * @return number of vertexes + * returns number of vertices in MeshFacet + * @return number of vertices */ - public int getNumberOfVertexes() { - return vertexes.size(); + public int getNumberOfVertices() { + return vertices.size(); + } + + /** + * returns list of vertices in MeshFacet + * @return list if vertices + */ + public List<MeshPoint> getVertices() { + return vertices; } /** diff --git a/MeshModel/src/main/java/cz/fidentis/analyst/mesh/core/MeshPoint.java b/MeshModel/src/main/java/cz/fidentis/analyst/mesh/core/MeshPoint.java index 4fdc8d9c212b45c4fdc3606da1dd1dddae138c01..159f1ea54d12c44558c51c13f8d89867460de955 100644 --- a/MeshModel/src/main/java/cz/fidentis/analyst/mesh/core/MeshPoint.java +++ b/MeshModel/src/main/java/cz/fidentis/analyst/mesh/core/MeshPoint.java @@ -12,13 +12,14 @@ public class MeshPoint { /** * constructor of meshPoint + * * @param position position of MeshPoint - * @param normal normal of MeshPoint + * @param normal normal of MeshPoint * @param texCoord coordinates in texture */ - public MeshPoint(Vector3d position, Vector3d normal , Vector3d texCoord) { - if (position == null || normal == null || texCoord == null) { - throw new NullPointerException("position, normal and texCoord cannot be null"); + public MeshPoint(Vector3d position, Vector3d normal, Vector3d texCoord) { + if (position == null) { + throw new NullPointerException("position cannot be null"); } this.position = position; @@ -47,6 +48,96 @@ public class MeshPoint { return texCoord; } + /** + * returns new instance of MeshPoint with subtracted position + * + * @param subtrahend position to be subtracted + * @return subtracted MeshPoint + */ + public MeshPoint subtractPosition(MeshPoint subtrahend) { + return subtractPosition(subtrahend.position); + } + + /** + * returns new instance of MeshPoint with subtracted position + * + * @param subtrahend position to be subtracted + * @return subtracted MeshPoint + */ + public MeshPoint subtractPosition(Vector3d subtrahend) { + Vector3d newPosition = new Vector3d(position); + newPosition.sub(subtrahend); + return new MeshPoint(new Vector3d(newPosition), normal, new Vector3d(texCoord)); + } + + /** + * returns new instance of MeshPoint with added position + * + * @param addend position to be added + * @return added MeshPoint + */ + public MeshPoint addPosition(MeshPoint addend) { + return addPosition(addend.position); + } + + /** + * returns new instance of MeshPoint with added position + * + * @param addend position to be added + * @return added MeshPoint + */ + public MeshPoint addPosition(Vector3d addend) { + Vector3d newPosition = new Vector3d(position); + newPosition.add(addend); + return new MeshPoint(new Vector3d(newPosition), normal, new Vector3d(texCoord)); + } + + /** + * returns new instance of MeshPoint with subtracted normal + * + * @param subtrahend normal to be subtracted + * @return subtracted MeshPoint + */ + public MeshPoint subtractNormal(MeshPoint subtrahend) { + return subtractNormal(subtrahend.normal); + } + + /** + * returns new instance of MeshPoint with subtracted normal + * + * @param subtrahend normal to be subtracted + * @return subtracted MeshPoint + */ + public MeshPoint subtractNormal(Vector3d subtrahend) { + Vector3d newNormal = new Vector3d(normal); + newNormal.sub(subtrahend); + newNormal.normalize(); + return new MeshPoint(new Vector3d(position), newNormal, new Vector3d(texCoord)); + } + + /** + * returns new instance of MeshPoint with added normal + * + * @param addend normal to be added + * @return added MeshPoint + */ + public MeshPoint addNormal(MeshPoint addend) { + return addNormal(addend.normal); + } + + /** + * returns new instance of MeshPoint with added normal + * + * @param addend normal to be added + * @return added MeshPoint + */ + public MeshPoint addNormal(Vector3d addend) { + Vector3d newNormal = new Vector3d(normal); + newNormal.add(addend); + newNormal.normalize(); + return new MeshPoint(new Vector3d(position), newNormal, new Vector3d(texCoord)); + } + /** * @param obj compared object * @return true if positions, normals and texture coordinates are equal, false otherwise @@ -65,6 +156,7 @@ public class MeshPoint { /** * returns hash of MeshPoint + * * @return hash */ @Override diff --git a/MeshModel/src/main/java/cz/fidentis/analyst/mesh/io/MeshObjLoader.java b/MeshModel/src/main/java/cz/fidentis/analyst/mesh/io/MeshObjLoader.java index 14737ea1e08fcf6f4fac5b928f40638783021cd4..14d6730fdfc092e7d7f5a4fe59ee72d032da0ef1 100644 --- a/MeshModel/src/main/java/cz/fidentis/analyst/mesh/io/MeshObjLoader.java +++ b/MeshModel/src/main/java/cz/fidentis/analyst/mesh/io/MeshObjLoader.java @@ -1,252 +1,234 @@ -package cz.fidentis.analyst.mesh.io; - -import com.mokiat.data.front.parser.IOBJParser; -import com.mokiat.data.front.parser.OBJDataReference; -import com.mokiat.data.front.parser.OBJFace; -import com.mokiat.data.front.parser.OBJMesh; -import com.mokiat.data.front.parser.OBJModel; -import com.mokiat.data.front.parser.OBJNormal; -import com.mokiat.data.front.parser.OBJObject; -import com.mokiat.data.front.parser.OBJParser; -import com.mokiat.data.front.parser.OBJTexCoord; -import com.mokiat.data.front.parser.OBJVertex; -import cz.fidentis.analyst.mesh.core.CornerTableRow; -import cz.fidentis.analyst.mesh.core.MeshFacet; -import cz.fidentis.analyst.mesh.core.MeshModel; -import cz.fidentis.analyst.mesh.core.MeshPoint; - -import javax.vecmath.Vector3d; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.InputStream; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import javax.vecmath.Vector3d; - - -/** - * Utility class for loading human face from OBJ data format. - * Expects only one human face in one file stored as one object. - * @author Marek Bařinka - */ -public class MeshObjLoader { - - /** - * Opens file and loads data into MeshModel - * @param file File containing face to load into a MeshModel - * @return Complete MeshModel - * @throws FileNotFoundException Requested file was not found - * @throws IOException There was problem with reading the file - */ - public static MeshModel read(File file) throws FileNotFoundException, IOException { - try (InputStream is = new FileInputStream(file)) { - return read(is); - } - } - - /** - * Loads data into MeshModel - * @param is Input data stream with model - * @return Complete MeshModel - * @throws IOException Data are corrupted - */ - public static MeshModel read(InputStream is) throws IOException { - final IOBJParser parser = new OBJParser(); - final OBJModel model = parser.parse(is); - - if (model.getObjects().isEmpty()) { - throw new IOException("File doesn't contain any model"); - } - OBJObject object = model.getObjects().get(0); - return parseObjectToModel(model, object); - } - - /** - * Parse OBJObject into MeshModel - * @param model Model is needed in future. It's holding data pools - * @param object Object to parse. It corespond to our MeshModel - * @return Returns complete model - * @throws IOException Data are corrupted - */ - private static MeshModel parseObjectToModel(OBJModel model, OBJObject object) throws IOException { - MeshModel meshModel = new MeshModel(); - // Our facet = loader mesh, create and fill all facets - for (OBJMesh mesh : object.getMeshes()) { - MeshFacet meshFacet = parseMeshToFacet(model, mesh); - meshModel.addFacet(meshFacet); - } - return meshModel; - } - - /** - * Parse OBJMesh into MeshFacet containig corner table data - * @param model Model is needed in future. It's holding data pools - * @param mesh Mesh to parse. It corespond to our MeshFacet - * @return Returns complete facet - * @throws IOException Data are corrupted - */ - private static MeshFacet parseMeshToFacet(OBJModel model, OBJMesh mesh) throws IOException { - MeshFacet meshFacet = new MeshFacet(); - Map<MeshPoint, Integer> vertices = new HashMap(); - Map<Edge, Integer> edges = new HashMap(); - - for (OBJFace face : mesh.getFaces()) { - processFace(model, face, meshFacet, vertices, edges); - } - - return meshFacet; - } - - /** - * Process one face in source data into data and insert them into CornerTable - * @param model Model is needed in future. It's holding data pools - * @param face Face to process - * @param meshFacet MeshFacet containing data pools - * @param vertices Map containing information about processed vertices - * and their index in CornerTable - * @param edges Map containing edges and index of their opposite vertex - * @throws IOException Data are corrupted - */ - - private static void processFace(OBJModel model, OBJFace face, MeshFacet meshFacet, - Map<MeshPoint, Integer> vertices, Map<Edge, Integer> edges) throws IOException { - List<MeshPoint> trianglePoints = parseFaceToTriangle(model, face); - List<Integer> vertexIndicies = new ArrayList(); - - // This cycle adds integer indices of new mesh points and add them to CornerTable - for (MeshPoint vertex : trianglePoints) { - Integer vertIndex = vertices.get(vertex); - if (vertIndex == null) { - int newIndex = meshFacet.getNumberOfVertexes(); - vertices.put(vertex, newIndex); - meshFacet.addVertex(vertex); - vertIndex = newIndex; - } - vertexIndicies.add(vertIndex); - CornerTableRow cornerTableRow = new CornerTableRow(vertIndex, -1); - meshFacet.getCornerTable().addRow(cornerTableRow); - } - - List<Edge> triangleEdges = new ArrayList(); - triangleEdges.add(new Edge(trianglePoints.get(0).getPosition(), - trianglePoints.get(1).getPosition(), vertexIndicies.get(2))); - triangleEdges.add(new Edge(trianglePoints.get(1).getPosition(), - trianglePoints.get(2).getPosition(), vertexIndicies.get(0))); - triangleEdges.add(new Edge(trianglePoints.get(2).getPosition(), - trianglePoints.get(0).getPosition(), vertexIndicies.get(1))); - - for (Edge e : triangleEdges) { - // We are processing edge which we already found - // We can set corner.opposite on both corners - if (edges.containsKey(e)) { - int oppositeCornerIndex = edges.get(e); - meshFacet.getCornerTable().getRow(oppositeCornerIndex).setOppositeCornerIndex(e.getCornerIndex()); - meshFacet.getCornerTable().getRow(e.getCornerIndex()).setOppositeCornerIndex(oppositeCornerIndex); - edges.remove(e); - } else { - edges.put(e, e.getCornerIndex()); - } - } - } - - /** - * Parse face from face data into list of MeshPoint - * @param model Model contains data pool - * @param face Face contains information about actually processed triangle - * @return List containing three MeshPoints parsed from face - * @throws IOException If face is non-triangular - */ - private static List<MeshPoint> parseFaceToTriangle(OBJModel model, OBJFace face) throws IOException { - List<MeshPoint> result = new ArrayList(); - - List<OBJDataReference> references = face.getReferences(); - - for (OBJDataReference reference : references) { - final OBJVertex vertex = model.getVertex(reference); - Vector3d coords = new Vector3d(vertex.x, vertex.y, vertex.z); - Vector3d norm = null; - Vector3d texCoords = null; - if (reference.hasNormalIndex()) { - final OBJNormal normal = model.getNormal(reference); - norm = new Vector3d(normal.x, normal.y, normal.z); - } - if (reference.hasTexCoordIndex()) { - final OBJTexCoord texCoord = model.getTexCoord(reference); - texCoords = new Vector3d(texCoord.u, texCoord.v, texCoord.w); - } - result.add(new MeshPoint(coords, norm, texCoords)); - } - if (result.size() != 3) { - throw new IOException("Mesh contains non-triangular face"); - } - return result; - } - - /** - * Helper class for finding opposite corners - * @author Marek Bařinka - */ - private static class Edge { - - private final Vector3d v1; - private final Vector3d v2; - private final int cornerIndex; - - public Edge(Vector3d v1, Vector3d v2, int cornerIndex) { - this.v1 = v1; - this.v2 = v2; - this.cornerIndex = cornerIndex; - } - - /** - * Returns new edge containing same vertices with opposite order - * and invalid cornerIndex value. - * @return Inverted edge in @method(equals) meaning - */ - public Edge getInvertedEdge() { - return new Edge(this.v2, this.v1, -1); - } - - public int getCornerIndex() { - return cornerIndex; - } - - /** - * Hash code must be generated this way because of @method(equals) - * @return hash code of edge - */ - @Override - public int hashCode() { - int hash = 3; - hash += Objects.hashCode(this.v1); - hash += Objects.hashCode(this.v2); - return hash; - } - - /** - * Two edges are considered same if they have same vertices. - * @param obj Other edge to test - * @return true if edges are same with opposite direction - */ - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - if (!(obj instanceof Edge)) { - return false; - } - final Edge other = (Edge) obj; - return (other.v1.equals(this.v1) && other.v2.equals(this.v2)) || - (other.v1.equals(this.v2) && other.v2.equals(this.v1)); - } - } +package cz.fidentis.analyst.mesh.io; + +import com.mokiat.data.front.parser.*; +import cz.fidentis.analyst.mesh.core.CornerTableRow; +import cz.fidentis.analyst.mesh.core.MeshFacet; +import cz.fidentis.analyst.mesh.core.MeshModel; +import cz.fidentis.analyst.mesh.core.MeshPoint; + +import javax.vecmath.Vector3d; +import java.io.*; +import java.util.*; + + +/** + * Utility class for loading human face from OBJ data format. + * Expects only one human face in one file stored as one object. + * @author Marek Bařinka + */ +public class MeshObjLoader { + + /** + * Opens file and loads data into MeshModel + * @param file File containing face to load into a MeshModel + * @return Complete MeshModel + * @throws FileNotFoundException Requested file was not found + * @throws IOException There was problem with reading the file + */ + public static MeshModel read(File file) throws FileNotFoundException, IOException { + try (InputStream is = new FileInputStream(file)) { + return read(is); + } + } + + /** + * Loads data into MeshModel + * @param is Input data stream with model + * @return Complete MeshModel + * @throws IOException Data are corrupted + */ + public static MeshModel read(InputStream is) throws IOException { + final IOBJParser parser = new OBJParser(); + final OBJModel model = parser.parse(is); + + if (model.getObjects().isEmpty()) { + throw new IOException("File doesn't contain any model"); + } + OBJObject object = model.getObjects().get(0); + return parseObjectToModel(model, object); + } + + /** + * Parse OBJObject into MeshModel + * @param model Model is needed in future. It's holding data pools + * @param object Object to parse. It corespond to our MeshModel + * @return Returns complete model + * @throws IOException Data are corrupted + */ + private static MeshModel parseObjectToModel(OBJModel model, OBJObject object) throws IOException { + MeshModel meshModel = new MeshModel(); + // Our facet = loader mesh, create and fill all facets + for (OBJMesh mesh : object.getMeshes()) { + MeshFacet meshFacet = parseMeshToFacet(model, mesh); + meshModel.addFacet(meshFacet); + } + return meshModel; + } + + /** + * Parse OBJMesh into MeshFacet containig corner table data + * @param model Model is needed in future. It's holding data pools + * @param mesh Mesh to parse. It corespond to our MeshFacet + * @return Returns complete facet + * @throws IOException Data are corrupted + */ + private static MeshFacet parseMeshToFacet(OBJModel model, OBJMesh mesh) throws IOException { + MeshFacet meshFacet = new MeshFacet(); + Map<MeshPoint, Integer> vertices = new HashMap(); + Map<Edge, Integer> edges = new HashMap(); + + for (OBJFace face : mesh.getFaces()) { + processFace(model, face, meshFacet, vertices, edges); + } + + return meshFacet; + } + + /** + * Process one face in source data into data and insert them into CornerTable + * @param model Model is needed in future. It's holding data pools + * @param face Face to process + * @param meshFacet MeshFacet containing data pools + * @param vertices Map containing information about processed vertices + * and their index in CornerTable + * @param edges Map containing edges and index of their opposite vertex + * @throws IOException Data are corrupted + */ + + private static void processFace(OBJModel model, OBJFace face, MeshFacet meshFacet, + Map<MeshPoint, Integer> vertices, Map<Edge, Integer> edges) throws IOException { + List<MeshPoint> trianglePoints = parseFaceToTriangle(model, face); + List<Integer> vertexIndicies = new ArrayList(); + + // This cycle adds integer indices of new mesh points and add them to CornerTable + for (MeshPoint vertex : trianglePoints) { + Integer vertIndex = vertices.get(vertex); + if (vertIndex == null) { + int newIndex = meshFacet.getNumberOfVertices(); + vertices.put(vertex, newIndex); + meshFacet.addVertex(vertex); + vertIndex = newIndex; + } + vertexIndicies.add(vertIndex); + CornerTableRow cornerTableRow = new CornerTableRow(vertIndex, -1); + meshFacet.getCornerTable().addRow(cornerTableRow); + } + + List<Edge> triangleEdges = new ArrayList(); + triangleEdges.add(new Edge(trianglePoints.get(0).getPosition(), + trianglePoints.get(1).getPosition(), vertexIndicies.get(2))); + triangleEdges.add(new Edge(trianglePoints.get(1).getPosition(), + trianglePoints.get(2).getPosition(), vertexIndicies.get(0))); + triangleEdges.add(new Edge(trianglePoints.get(2).getPosition(), + trianglePoints.get(0).getPosition(), vertexIndicies.get(1))); + + for (Edge e : triangleEdges) { + // We are processing edge which we already found + // We can set corner.opposite on both corners + if (edges.containsKey(e)) { + int oppositeCornerIndex = edges.get(e); + meshFacet.getCornerTable().getRow(oppositeCornerIndex).setOppositeCornerIndex(e.getCornerIndex()); + meshFacet.getCornerTable().getRow(e.getCornerIndex()).setOppositeCornerIndex(oppositeCornerIndex); + edges.remove(e); + } else { + edges.put(e, e.getCornerIndex()); + } + } + } + + /** + * Parse face from face data into list of MeshPoint + * @param model Model contains data pool + * @param face Face contains information about actually processed triangle + * @return List containing three MeshPoints parsed from face + * @throws IOException If face is non-triangular + */ + private static List<MeshPoint> parseFaceToTriangle(OBJModel model, OBJFace face) throws IOException { + List<MeshPoint> result = new ArrayList(); + + List<OBJDataReference> references = face.getReferences(); + + for (OBJDataReference reference : references) { + final OBJVertex vertex = model.getVertex(reference); + Vector3d coords = new Vector3d(vertex.x, vertex.y, vertex.z); + Vector3d norm = null; + Vector3d texCoords = null; + if (reference.hasNormalIndex()) { + final OBJNormal normal = model.getNormal(reference); + norm = new Vector3d(normal.x, normal.y, normal.z); + } + if (reference.hasTexCoordIndex()) { + final OBJTexCoord texCoord = model.getTexCoord(reference); + texCoords = new Vector3d(texCoord.u, texCoord.v, texCoord.w); + } + result.add(new MeshPoint(coords, norm, texCoords)); + } + if (result.size() != 3) { + throw new IOException("Mesh contains non-triangular face"); + } + return result; + } + + /** + * Helper class for finding opposite corners + * @author Marek Bařinka + */ + private static class Edge { + + private final Vector3d v1; + private final Vector3d v2; + private final int cornerIndex; + + public Edge(Vector3d v1, Vector3d v2, int cornerIndex) { + this.v1 = v1; + this.v2 = v2; + this.cornerIndex = cornerIndex; + } + + /** + * Returns new edge containing same vertices with opposite order + * and invalid cornerIndex value. + * @return Inverted edge in @method(equals) meaning + */ + public Edge getInvertedEdge() { + return new Edge(this.v2, this.v1, -1); + } + + public int getCornerIndex() { + return cornerIndex; + } + + /** + * Hash code must be generated this way because of @method(equals) + * @return hash code of edge + */ + @Override + public int hashCode() { + int hash = 3; + hash += Objects.hashCode(this.v1); + hash += Objects.hashCode(this.v2); + return hash; + } + + /** + * Two edges are considered same if they have same vertices. + * @param obj Other edge to test + * @return true if edges are same with opposite direction + */ + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (!(obj instanceof Edge)) { + return false; + } + final Edge other = (Edge) obj; + return (other.v1.equals(this.v1) && other.v2.equals(this.v2)) || + (other.v1.equals(this.v2) && other.v2.equals(this.v1)); + } + } } \ No newline at end of file