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> </dependencies>
<build> <build>
<!-- <testSourceDirectory>src/tests</testSourceDirectory> --> <testSourceDirectory>src/tests</testSourceDirectory>
<plugins> <plugins>
<plugin> <plugin>
<groupId>org.codehaus.mojo</groupId> <groupId>org.codehaus.mojo</groupId>

View File

@ -66,7 +66,8 @@ public class OBJFile {
}; };
public boolean loadModelFromFilename(String file) { public boolean loadModelFromFilename(String file) {
//URL fileURL = getClass().getClassLoader().getResource(File.separator + file); // URL fileURL = getClass().getClassLoader().getResource(File.separator
// + file);
URL fileURL = null; URL fileURL = null;
try { try {
@ -98,102 +99,18 @@ public class OBJFile {
boolean hasNormals = false; boolean hasNormals = false;
while ((line = input.readLine()) != null) { while ((line = input.readLine()) != null) {
if(line.isEmpty()){ if (line.isEmpty()) {
continue; continue;
} }
switch (line.charAt(0)) { switch (line.charAt(0)) {
case '#': case '#':
break; break;
case 'v': case 'v':
switch (line.charAt(1)) { parseObjVertex(line, val);
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;
}
break; break;
case 'f': case 'f':
// face hasNormals = parseObjFace(line, idx, 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);
}
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;
}
break; break;
default: default:
break; break;
} }
@ -223,8 +140,7 @@ public class OBJFile {
} catch (IOException closee) { } catch (IOException closee) {
} }
} }
} } else {
else{
logger.error("URL was null"); 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 * v 1.0 0.0 0.0
* 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 * Vertex/Normal Format
* a simple triangle mesh with no connectivity. *
* <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() { public void compileModel() {
logger.info("expect pIndex.size: " + pIndex_.size() + " Nindex.size:" + nIndex_.size());
boolean needsTriangles = true; boolean needsTriangles = true;
HashMap<IdxSet, Integer> pts = new HashMap<IdxSet, Integer>(); 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() { public BoundingBox3d computeBoundingBox() {
float[] minVal = new float[3]; float[] minVal = new float[3];
@ -398,6 +475,10 @@ public class OBJFile {
return openEdges_; 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> positions_ = new ArrayList<Float>();
protected List<Float> normals_ = new ArrayList<Float>(); protected List<Float> normals_ = new ArrayList<Float>();
protected int posSize_; 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.BoundingBox3d;
import org.jzy3d.maths.Coord3d; import org.jzy3d.maths.Coord3d;
import org.jzy3d.plot3d.primitives.AbstractDrawable; import org.jzy3d.plot3d.primitives.AbstractDrawable;
import org.jzy3d.plot3d.primitives.IGLBindedResource;
import org.jzy3d.plot3d.primitives.AbstractGeometry.PolygonMode; 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.primitives.vbo.buffers.FloatVBO;
import org.jzy3d.plot3d.rendering.canvas.Quality; import org.jzy3d.plot3d.rendering.canvas.Quality;
import org.jzy3d.plot3d.rendering.compat.GLES2CompatUtils; import org.jzy3d.plot3d.rendering.compat.GLES2CompatUtils;

View File

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

View File

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

View File

@ -1,7 +1,6 @@
package org.jzy3d.plot3d.primitives.vbo.drawable; package org.jzy3d.plot3d.primitives.vbo.drawable;
import org.jzy3d.io.IGLLoader; import org.jzy3d.io.IGLLoader;
import org.jzy3d.plot3d.primitives.vbo.drawable.DrawableVBO;
import com.jogamp.opengl.GL2; 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.ordering.AbstractOrderingStrategy;
import org.jzy3d.plot3d.rendering.view.View; import org.jzy3d.plot3d.rendering.view.View;
import com.jogamp.nativewindow.ScalableSurface;
import com.jogamp.opengl.util.Animator; import com.jogamp.opengl.util.Animator;
/** Provides a structure for setting the rendering quality, i.e., the tradeoff /** 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();
}
}