ObjFile support face format where normal are not defined ("/" separator

not present)
This commit is contained in:
Martin Pernollet 2017-05-06 19:54:29 +02:00
parent adacd5b6aa
commit 71767f6489
9 changed files with 266 additions and 112 deletions

View File

@ -80,7 +80,7 @@
</dependencies>
<build>
<!-- <testSourceDirectory>src/tests</testSourceDirectory> -->
<testSourceDirectory>src/tests</testSourceDirectory>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>

View File

@ -66,7 +66,8 @@ public class OBJFile {
};
public boolean loadModelFromFilename(String file) {
//URL fileURL = getClass().getClassLoader().getResource(File.separator + file);
// URL fileURL = getClass().getClassLoader().getResource(File.separator
// + file);
URL fileURL = null;
try {
@ -98,102 +99,18 @@ public class OBJFile {
boolean hasNormals = false;
while ((line = input.readLine()) != null) {
if(line.isEmpty()){
if (line.isEmpty()) {
continue;
}
switch (line.charAt(0)) {
case '#':
break;
case 'v':
switch (line.charAt(1)) {
case ' ':
line = line.substring(line.indexOf(" ") + 1);
// vertex, 3 or 4 components
val[0] = Float.valueOf(line.substring(0, line.indexOf(" ")));
line = line.substring(line.indexOf(" ") + 1);
val[1] = Float.valueOf(line.substring(0, line.indexOf(" ")));
line = line.substring(line.indexOf(" ") + 1);
val[2] = Float.valueOf(line);
positions_.add(val[0]);
positions_.add(val[1]);
positions_.add(val[2]);
break;
case 'n':
// normal, 3 components
line = line.substring(line.indexOf(" ") + 1);
val[0] = Float.valueOf(line.substring(0, line.indexOf(" ")));
line = line.substring(line.indexOf(" ") + 1);
val[1] = Float.valueOf(line.substring(0, line.indexOf(" ")));
line = line.substring(line.indexOf(" ") + 1);
val[2] = Float.valueOf(line);
normals_.add(val[0]);
normals_.add(val[1]);
normals_.add(val[2]);
break;
}
parseObjVertex(line, val);
break;
case 'f':
// face
line = line.substring(line.indexOf(" ") + 1);
// Remove any additional leading whitespace. The whitespace count
// can vary for different programs, i.e., Right Hemisphere produces
// obj files with 2 spaces. Meshlab produces single spaced files.
while(line.startsWith(" ")){
line = line.substring(1);
}
idx[0][0] = Integer.valueOf(line.substring(0, line.indexOf("//"))).intValue();
line = line.substring(line.indexOf("//") + 2);
idx[0][1] = Integer.valueOf(line.substring(0, line.indexOf(" "))).intValue();
{
// This face has vertex and normal indices
// in .obj, f v1 .... the vertex index used start
// from 1, so -1 here
// remap them to the right spot
idx[0][0] = (idx[0][0] > 0) ? (idx[0][0] - 1) : (positions_.size() - idx[0][0]);
idx[0][1] = (idx[0][1] > 0) ? (idx[0][1] - 1) : (normals_.size() - idx[0][1]);
// grab the second vertex to prime
line = line.substring(line.indexOf(" ") + 1);
idx[1][0] = Integer.valueOf(line.substring(0, line.indexOf("//")));
line = line.substring(line.indexOf("//") + 2);
idx[1][1] = Integer.valueOf(line.substring(0, line.indexOf(" ")));
// remap them to the right spot
idx[1][0] = (idx[1][0] > 0) ? (idx[1][0] - 1) : (positions_.size() - idx[1][0]);
idx[1][1] = (idx[1][1] > 0) ? (idx[1][1] - 1) : (normals_.size() - idx[1][1]);
// grab the third vertex to prime
line = line.substring(line.indexOf(" ") + 1);
idx[2][0] = Integer.valueOf(line.substring(0, line.indexOf("//")));
line = line.substring(line.indexOf("//") + 2);
idx[2][1] = Integer.valueOf(line);
{
// remap them to the right spot
idx[2][0] = (idx[2][0] > 0) ? (idx[2][0] - 1) : (positions_.size() - idx[2][0]);
idx[2][1] = (idx[2][1] > 0) ? (idx[2][1] - 1) : (normals_.size() - idx[2][1]);
// add the indices
for (int ii = 0; ii < 3; ii++) {
pIndex_.add(idx[ii][0]);
nIndex_.add(idx[ii][1]);
}
// prepare for the next iteration, the num 0
// does not change.
idx[1][0] = idx[2][0];
idx[1][1] = idx[2][1];
}
hasNormals = true;
}
hasNormals = parseObjFace(line, idx, hasNormals);
break;
default:
break;
}
@ -223,8 +140,7 @@ public class OBJFile {
} catch (IOException closee) {
}
}
}
else{
} else {
logger.error("URL was null");
}
@ -232,16 +148,176 @@ public class OBJFile {
}
/**
* This function takes the raw model data in the internal
* structures, and attempts to bring it to a format directly
* accepted for vertex array style rendering. This means that
* a unique compiled vertex will exist for each unique
* combination of position, normal, tex coords, etc that are
* used in the model. The prim parameter, tells the model
* what type of index list to compile. By default it compiles
* a simple triangle mesh with no connectivity.
*
* v 1.0 0.0 0.0
*
*/
/**
* Vertex/Normal Format
*
* <pre>
* v 1.0 0.0 0.0
* vn 0.0 1.0 0.0
*
* n : normal
*
* </pre>
*
* @see https://fr.wikipedia.org/wiki/Objet_3D_%28format_de_fichier%29
*/
public void parseObjVertex(String line, float[] val) {
switch (line.charAt(1)) {
case ' ':
// logger.info(line);
line = line.substring(line.indexOf(" ") + 1);
// vertex, 3 or 4 components
val[0] = Float.valueOf(line.substring(0, line.indexOf(" ")));
line = line.substring(line.indexOf(" ") + 1);
val[1] = Float.valueOf(line.substring(0, line.indexOf(" ")));
line = line.substring(line.indexOf(" ") + 1);
val[2] = Float.valueOf(line);
positions_.add(val[0]);
positions_.add(val[1]);
positions_.add(val[2]);
break;
case 'n':
// normal, 3 components
line = line.substring(line.indexOf(" ") + 1);
val[0] = Float.valueOf(line.substring(0, line.indexOf(" ")));
line = line.substring(line.indexOf(" ") + 1);
val[1] = Float.valueOf(line.substring(0, line.indexOf(" ")));
line = line.substring(line.indexOf(" ") + 1);
val[2] = Float.valueOf(line);
normals_.add(val[0]);
normals_.add(val[1]);
normals_.add(val[2]);
break;
}
}
/**
* Face Format
*
* <pre>
* f v1/vt1/vn1 v2/vt2/vn2 v3/vt3/vn3
*
* t : texture
* n : normal
*
* </pre>
*
*/
public boolean parseObjFace(String line, int[][] idx, boolean hasNormals) {
line = line.substring(line.indexOf(" ") + 1);
// Remove any additional leading whitespace. The whitespace count
// can vary for different programs, i.e., Right Hemisphere produces
// obj files with 2 spaces. Meshlab produces single spaced files.
while (line.startsWith(" ")) {
line = line.substring(1);
}
// If following pattern "f 9227//9524 8376//8650 8377//8649"
if (ObjFaceFormat.vertexNormal(line)) {
idx[0][0] = Integer.valueOf(line.substring(0, line.indexOf("//"))).intValue();
line = line.substring(line.indexOf("//") + 2);
idx[0][1] = Integer.valueOf(line.substring(0, line.indexOf(" "))).intValue();
{
// This face has vertex and normal indices
// in .obj, f v1 .... the vertex index used start
// from 1, so -1 here
// remap them to the right spot
idx[0][0] = (idx[0][0] > 0) ? (idx[0][0] - 1) : (positions_.size() - idx[0][0]);
idx[0][1] = (idx[0][1] > 0) ? (idx[0][1] - 1) : (normals_.size() - idx[0][1]);
// grab the second vertex to prime
line = line.substring(line.indexOf(" ") + 1);
idx[1][0] = Integer.valueOf(line.substring(0, line.indexOf("//")));
line = line.substring(line.indexOf("//") + 2);
idx[1][1] = Integer.valueOf(line.substring(0, line.indexOf(" ")));
// remap them to the right spot
idx[1][0] = (idx[1][0] > 0) ? (idx[1][0] - 1) : (positions_.size() - idx[1][0]);
idx[1][1] = (idx[1][1] > 0) ? (idx[1][1] - 1) : (normals_.size() - idx[1][1]);
// grab the third vertex to prime
line = line.substring(line.indexOf(" ") + 1);
idx[2][0] = Integer.valueOf(line.substring(0, line.indexOf("//")));
line = line.substring(line.indexOf("//") + 2);
idx[2][1] = Integer.valueOf(line);
{
// remap them to the right spot
idx[2][0] = (idx[2][0] > 0) ? (idx[2][0] - 1) : (positions_.size() - idx[2][0]);
idx[2][1] = (idx[2][1] > 0) ? (idx[2][1] - 1) : (normals_.size() - idx[2][1]);
// add the indices
for (int ii = 0; ii < 3; ii++) {
pIndex_.add(idx[ii][0]);
nIndex_.add(idx[ii][1]);
}
// prepare for the next iteration, the num 0
// does not change.
idx[1][0] = idx[2][0];
idx[1][1] = idx[2][1];
}
hasNormals = true;
}
}
// Not following pattern "f 9227//9524 8376//8650 8377//8649"
else if (ObjFaceFormat.vertex(line)) {
String[] nums = line.split(" ");
idx[0][0] = Integer.parseInt(nums[0]);
// in .obj, f v1 .... the vertex index used start
// from 1, so -1 here
// remap them to the right spot
idx[0][0] = (idx[0][0] > 0) ? (idx[0][0] - 1) : (positions_.size() - idx[0][0]);
idx[1][0] = Integer.parseInt(nums[1]);
// remap them to the right spot
idx[1][0] = (idx[1][0] > 0) ? (idx[1][0] - 1) : (positions_.size() - idx[1][0]);
idx[2][0] = Integer.parseInt(nums[2]);
// remap them to the right spot
idx[2][0] = (idx[2][0] > 0) ? (idx[2][0] - 1) : (positions_.size() - idx[2][0]);
// add the indices
for (int ii = 0; ii < 3; ii++) {
pIndex_.add(idx[ii][0]);
}
// prepare for the next iteration, the num 0
// does not change.
idx[1][0] = idx[2][0];
// logger.info("FACE - " + line);
}
return hasNormals;
}
/**
* This function takes the raw model data in the internal structures, and
* attempts to bring it to a format directly accepted for vertex array style
* rendering. This means that a unique compiled vertex will exist for each
* unique combination of position, normal, tex coords, etc that are used in
* the model. The prim parameter, tells the model what type of index list to
* compile. By default it compiles a simple triangle mesh with no
* connectivity.
*/
public void compileModel() {
logger.info("expect pIndex.size: " + pIndex_.size() + " Nindex.size:" + nIndex_.size());
boolean needsTriangles = true;
HashMap<IdxSet, Integer> pts = new HashMap<IdxSet, Integer>();
@ -296,7 +372,8 @@ public class OBJFile {
}
/**
* Returns the points defining the axis-aligned bounding box containing the model.
* Returns the points defining the axis-aligned bounding box containing the
* model.
*/
public BoundingBox3d computeBoundingBox() {
float[] minVal = new float[3];
@ -398,6 +475,10 @@ public class OBJFile {
return openEdges_;
}
public String toString() {
return "countVertices =" + getPositionCount() + " indexSize = " + getIndexCount() + " vertexSize = " + getCompiledVertexCount() + " byteOffset = " + getCompiledVertexSize() + " normalOffset = " + getCompiledNormalOffset() + " dimensions = " + getPositionSize();
}
protected List<Float> positions_ = new ArrayList<Float>();
protected List<Float> normals_ = new ArrayList<Float>();
protected int posSize_;

View File

@ -0,0 +1,18 @@
package org.jzy3d.io.obj;
public class ObjFaceFormat {
/** f v1/vt1/vn1 v2/vt2/vn2 v3/vt3/vn3 */
public boolean vertexTextureNormal(String line){
return false;
}
/** f v1//vn1 v2//vn2 v3//vn3 */
public static boolean vertexNormal(String line){
return line.indexOf("//")!=-1;
}
/** f v1 v2 v3 */
public static boolean vertex(String line){
return !line.contains("/");
}
}

View File

@ -9,8 +9,8 @@ import org.jzy3d.io.IGLLoader;
import org.jzy3d.maths.BoundingBox3d;
import org.jzy3d.maths.Coord3d;
import org.jzy3d.plot3d.primitives.AbstractDrawable;
import org.jzy3d.plot3d.primitives.IGLBindedResource;
import org.jzy3d.plot3d.primitives.AbstractGeometry.PolygonMode;
import org.jzy3d.plot3d.primitives.IGLBindedResource;
import org.jzy3d.plot3d.primitives.vbo.buffers.FloatVBO;
import org.jzy3d.plot3d.rendering.canvas.Quality;
import org.jzy3d.plot3d.rendering.compat.GLES2CompatUtils;

View File

@ -1,7 +1,6 @@
package org.jzy3d.plot3d.primitives.vbo.drawable;
import org.jzy3d.io.IGLLoader;
import org.jzy3d.plot3d.primitives.vbo.drawable.DrawableVBO;
import com.jogamp.opengl.GL2;

View File

@ -1,7 +1,6 @@
package org.jzy3d.plot3d.primitives.vbo.drawable;
import org.jzy3d.io.IGLLoader;
import org.jzy3d.plot3d.primitives.vbo.drawable.DrawableVBO;
import com.jogamp.opengl.GL2;

View File

@ -1,7 +1,6 @@
package org.jzy3d.plot3d.primitives.vbo.drawable;
import org.jzy3d.io.IGLLoader;
import org.jzy3d.plot3d.primitives.vbo.drawable.DrawableVBO;
import com.jogamp.opengl.GL2;

View File

@ -3,7 +3,6 @@ package org.jzy3d.plot3d.rendering.canvas;
import org.jzy3d.plot3d.rendering.ordering.AbstractOrderingStrategy;
import org.jzy3d.plot3d.rendering.view.View;
import com.jogamp.nativewindow.ScalableSurface;
import com.jogamp.opengl.util.Animator;
/** Provides a structure for setting the rendering quality, i.e., the tradeoff

View File

@ -0,0 +1,59 @@
package org.jzy3d.io.obj;
import java.io.File;
import org.junit.Assert;
import org.junit.Test;
public class TestObjFile {
@Test
public void loadDragon(){
OBJFile objFile = new OBJFile();
String objFilePath = "data/objfiles/dragon.obj";
objFile.loadModelFromFilename("file://" + new File(objFilePath).getAbsolutePath());
int countVertices = objFile.getPositionCount();
int indexSize = objFile.getIndexCount();
int vertexSize = objFile.getCompiledVertexCount();
int byteOffset = objFile.getCompiledVertexSize();
int normalOffset = objFile.getCompiledNormalOffset();
int dimensions = objFile.getPositionSize();
Assert.assertTrue(objFile.hasNormals());
Assert.assertEquals(437645, countVertices);
Assert.assertEquals(2614242, indexSize);
Assert.assertEquals(15685452, vertexSize);
Assert.assertEquals(0, byteOffset);
Assert.assertEquals(-1, normalOffset);
Assert.assertEquals(3, dimensions);
}
@Test
public void loadBunny(){
OBJFile objFile = new OBJFile();
String objFilePath = "data/objfiles/bunny.obj";
objFile.loadModelFromFilename("file://" + new File(objFilePath).getAbsolutePath());
int countVertices = objFile.getPositionCount();
int indexSize = objFile.getIndexCount();
int vertexSize = objFile.getCompiledVertexCount();
int byteOffset = objFile.getCompiledVertexSize();
int normalOffset = objFile.getCompiledNormalOffset();
int dimensions = objFile.getPositionSize();
Assert.assertFalse(objFile.hasNormals());
Assert.assertEquals(80289, countVertices);
Assert.assertEquals(482409, indexSize);
Assert.assertEquals(1447227, vertexSize);
Assert.assertEquals(0, byteOffset);
Assert.assertEquals(-1, normalOffset);
Assert.assertEquals(3, dimensions);
objFile.compileModel();
}
}