aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJulien Gouesse <[email protected]>2023-03-07 00:32:36 +0100
committerJulien Gouesse <[email protected]>2023-03-07 00:32:36 +0100
commit275f2eb1ce54e7a719717324c13e8685a1ac67bd (patch)
treee9c36e1c156964d422df39a92d95853ed2512ada
parentd8da0b55b161873e7bee159d4d9e5f4dec510270 (diff)
Improves the OFF importer, which can now support meshes with colors on vertices
-rw-r--r--ardor3d-examples/src/main/java/com/ardor3d/example/pipeline/SimpleOffExample.java2
-rw-r--r--ardor3d-extras/src/main/java/com/ardor3d/extension/model/off/OffFaceInfo.java1
-rw-r--r--ardor3d-extras/src/main/java/com/ardor3d/extension/model/off/OffImporter.java341
3 files changed, 301 insertions, 43 deletions
diff --git a/ardor3d-examples/src/main/java/com/ardor3d/example/pipeline/SimpleOffExample.java b/ardor3d-examples/src/main/java/com/ardor3d/example/pipeline/SimpleOffExample.java
index c8b3f19..be255dc 100644
--- a/ardor3d-examples/src/main/java/com/ardor3d/example/pipeline/SimpleOffExample.java
+++ b/ardor3d-examples/src/main/java/com/ardor3d/example/pipeline/SimpleOffExample.java
@@ -36,7 +36,7 @@ public class SimpleOffExample extends ExampleBase {
// Load the OFF scene
final long time = System.currentTimeMillis();
final OffImporter importer = new OffImporter();
- final OffGeometryStore storage = importer.load("off/cube.off");
+ final OffGeometryStore storage = importer.load("off/vertcube.off");
System.out.println("Importing Took " + (System.currentTimeMillis() - time) + " ms");
final Node model = storage.getScene();
diff --git a/ardor3d-extras/src/main/java/com/ardor3d/extension/model/off/OffFaceInfo.java b/ardor3d-extras/src/main/java/com/ardor3d/extension/model/off/OffFaceInfo.java
index d0cec71..60d24a7 100644
--- a/ardor3d-extras/src/main/java/com/ardor3d/extension/model/off/OffFaceInfo.java
+++ b/ardor3d-extras/src/main/java/com/ardor3d/extension/model/off/OffFaceInfo.java
@@ -19,6 +19,7 @@ public class OffFaceInfo {
private List<Integer> _materialIndices;
+ // TODO use only vertices indices and colors in faces
private List<Float> _textureCoordinates;
public OffFaceInfo() {
diff --git a/ardor3d-extras/src/main/java/com/ardor3d/extension/model/off/OffImporter.java b/ardor3d-extras/src/main/java/com/ardor3d/extension/model/off/OffImporter.java
index b63a3b0..74c44e3 100644
--- a/ardor3d-extras/src/main/java/com/ardor3d/extension/model/off/OffImporter.java
+++ b/ardor3d-extras/src/main/java/com/ardor3d/extension/model/off/OffImporter.java
@@ -17,17 +17,26 @@ import java.io.InputStreamReader;
import java.io.Reader;
import java.io.StreamTokenizer;
import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Optional;
import java.util.logging.Level;
import java.util.logging.Logger;
-import java.util.stream.Stream;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+import com.ardor3d.math.ColorRGBA;
+import com.ardor3d.math.Vector2;
+import com.ardor3d.math.Vector3;
import com.ardor3d.util.geom.GeometryTool;
import com.ardor3d.util.resource.ResourceLocator;
import com.ardor3d.util.resource.ResourceLocatorTool;
import com.ardor3d.util.resource.ResourceSource;
/**
- * OFF importer. See <a href="http://paulbourke.net/dataformats/off/">the format spec</a>
+ * OFF importer. See <a href="http://paulbourke.net/dataformats/off/">the format spec</a> and
+ * <a href="http://www.geomview.org/docs/html/OFF.html">Geomview's documentation</a>
*
* N.B: WORK IN PROGRESS, STILL A LOT OF WORK TO DO
*
@@ -37,6 +46,129 @@ import com.ardor3d.util.resource.ResourceSource;
*/
public class OffImporter {
+ private enum OffKeyword {
+ /**
+ *
+ */
+ _OFF("off", 3, 0, 0, 0),
+ /**
+ *
+ */
+ _COFF("coff", 3, 0, 4, 0),
+ /**
+ *
+ */
+ _CNOFF("cnoff", 3, 3, 4, 0),
+ /**
+ *
+ */
+ _NOFF("noff", 3, 3, 0, 0),
+ /**
+ *
+ */
+ _STCOFF("coff", 3, 0, 4, 2),
+ /**
+ *
+ */
+ _STCNOFF("cnoff", 3, 3, 4, 2),
+ /**
+ *
+ */
+ _STNOFF("noff", 3, 3, 0, 2),
+ /**
+ *
+ */
+ _4OFF("4off", 4, 0, 0, 0),
+ /**
+ *
+ */
+ _C4OFF("c4off", 4, 0, 4, 0),
+ /**
+ *
+ */
+ _CN4OFF("cn4off", 4, 4, 4, 0),
+ /**
+ *
+ */
+ _N4OFF("n4off", 4, 4, 0, 0),
+ /**
+ *
+ */
+ _STC4OFF("c4off", 4, 0, 4, 2),
+ /**
+ *
+ */
+ _STCN4OFF("cn4off", 4, 4, 4, 2),
+ /**
+ *
+ */
+ _STN4OFF("n4off", 4, 4, 0, 2);
+
+ /**
+ * lowercase text of the keyword as found in the very beginning of the file
+ */
+ private final String lowercaseKeywordText;
+
+ /**
+ * vertex values per tuple, either 3 or 4
+ */
+ private final int vertexValuesPerTuple;
+
+ /**
+ * normal values per tuple, either 0, 3 or 4
+ */
+ private final int normalValuesPerTuple;
+
+ /**
+ * minimum color values per tuple, either 0 or 1
+ */
+ private final int minColorValuesPerTuple;
+
+ /**
+ * maximum color values per tuple, either 0, 1 or 4, color map, RGB and RGBA colors are supported
+ */
+ private final int maxColorValuesPerTuple;
+
+ /**
+ * texture values per tuple, either 0 or 2
+ */
+ private final int textureValuesPerTuple;
+
+ private OffKeyword(final String lowercaseKeywordText, final int vertexValuesPerTuple,
+ final int normalValuesPerTuple, final int maxColorValuesPerTuple, final int textureValuesPerTuple) {
+ this.lowercaseKeywordText = lowercaseKeywordText;
+ this.vertexValuesPerTuple = vertexValuesPerTuple;
+ this.normalValuesPerTuple = normalValuesPerTuple;
+ minColorValuesPerTuple = Math.min(1, maxColorValuesPerTuple);
+ this.maxColorValuesPerTuple = maxColorValuesPerTuple;
+ this.textureValuesPerTuple = textureValuesPerTuple;
+ }
+
+ public String getLowercaseKeywordText() {
+ return lowercaseKeywordText;
+ }
+
+ public int getVertexValuesPerTuple() {
+ return vertexValuesPerTuple;
+ }
+
+ public int getNormalValuesPerTuple() {
+ return normalValuesPerTuple;
+ }
+
+ public int getMinColorValuesPerTuple() {
+ return minColorValuesPerTuple;
+ }
+
+ public int getMaxColorValuesPerTuple() {
+ return maxColorValuesPerTuple;
+ }
+
+ public int getTextureValuesPerTuple() {
+ return textureValuesPerTuple;
+ }
+ }
+
// public static interface OffReader extends Closeable {
// public double read() throws IOException;
// }
@@ -210,8 +342,8 @@ public class OffImporter {
new BufferedReader(new InputStreamReader(resource.openStream(), StandardCharsets.US_ASCII)))) {
try {
final Integer numberOfVertices;
- Integer numberOfFaces = null;
- Integer numberOfEdges = null;
+ final Integer numberOfFaces;
+ final Integer numberOfEdges;
// starts reading the file as ascii (binary file not supported)
// skips the commented line(s)
do {
@@ -223,15 +355,17 @@ public class OffImporter {
"Premature end of file, expected an optional off keyword followed by three integers vertex_count face_count edge_count");
}
final String unhandledFirstParsedValue;
- final int coordinateCountPerVertex;
- // tries to read "off", "coff", "noff", "cnoff" or "4off" (optional)
- if (Stream.of("off", "coff", "noff", "cnoff", "4off").anyMatch(parser.sval::equals)) {
- OffImporter.LOGGER.log(Level.INFO, parser.sval + " keyword on line " + parser.lineno());
- unhandledFirstParsedValue = null;
- } else {
+ final OffKeyword offKeywordInFile = Arrays.stream(OffKeyword.values()).filter(
+ (final OffKeyword offKeyword) -> offKeyword.getLowercaseKeywordText().equals(parser.sval))
+ .findFirst().orElse(null);
+ // tries to read an (optional) off keyword
+ if (offKeywordInFile == null) {
// no *off keyword
OffImporter.LOGGER.log(Level.INFO, "No off keyword on line " + parser.lineno());
unhandledFirstParsedValue = parser.sval;
+ } else {
+ OffImporter.LOGGER.log(Level.INFO, parser.sval + " keyword on line " + parser.lineno());
+ unhandledFirstParsedValue = null;
}
parser.nextToken();
if (parser.ttype == StreamTokenizer.TT_EOF) {
@@ -285,6 +419,7 @@ public class OffImporter {
parser.nextToken();
if (parser.ttype == StreamTokenizer.TT_WORD) {
numberOfEdges = Integer.valueOf(parser.sval);
+ OffImporter.LOGGER.log(Level.INFO, "Number of edges: " + numberOfEdges);
} else {
throw new IOException(
"Premature end of line, expected three integers vertex_count face_count edge_count");
@@ -300,51 +435,173 @@ public class OffImporter {
if (parser.ttype == StreamTokenizer.TT_EOF) {
throw new IOException("Premature end of file, no vertex has been declared");
}
- // reads the vertex coordinates
- // TODO read the color and normal coordinates on vertices
+ // when there is no off keyword in the file, it takes off keyword's behaviour
+ final OffKeyword offKeyword = Optional.ofNullable(offKeywordInFile).orElse(OffKeyword._OFF);
+ // computes the expected value counts per line of vertex definition
+ final int expectedValueCountPerVertexLineExcludingColorValues = offKeyword.getVertexValuesPerTuple()
+ + offKeyword.getNormalValuesPerTuple() + offKeyword.getTextureValuesPerTuple();
+ final int minExpectedValueCountPerVertexLine = expectedValueCountPerVertexLineExcludingColorValues
+ + offKeyword.getMinColorValuesPerTuple();
+ final int maxExpectedValueCountPerVertexLine = expectedValueCountPerVertexLineExcludingColorValues
+ + offKeyword.getMaxColorValuesPerTuple();
+ // reads the vertices, normals, colors and/or texture coordinates for each vertex in that order
for (int vertexIndex = 0; vertexIndex < numberOfVertices.intValue(); vertexIndex++) {
+ // skips comment lines, comments and empty lines
do {
parser.nextToken();
- } while (parser.ttype != StreamTokenizer.TT_WORD && parser.ttype != StreamTokenizer.TT_EOF
- && parser.ttype != StreamTokenizer.TT_EOL);
- // expects between 1 and 4 vertex coordinates per line
- if (parser.ttype == StreamTokenizer.TT_EOF || parser.ttype == StreamTokenizer.TT_EOL) {
- throw new IOException("Premature end of line, expected 1, 2, 3 or 4 vertex coordinates");
- }
- final Double x, y, z, w;
- x = Double.valueOf(parser.sval);
- parser.nextToken();
+ } while (parser.ttype != StreamTokenizer.TT_WORD && parser.ttype != StreamTokenizer.TT_EOF);
if (parser.ttype == StreamTokenizer.TT_EOF) {
- throw new IOException("Premature end of file, no face has been declared");
+ throw new IOException("Premature end of file, expected " + numberOfVertices.intValue()
+ + " vertices, found " + vertexIndex);
}
- if (parser.ttype == StreamTokenizer.TT_WORD) {
- y = Double.valueOf(parser.sval).doubleValue();
+ // keeps the current word
+ parser.pushBack();
+ // reads as much numbers as possible on the line
+ boolean goOn = true;
+ final List<Number> valueList = new ArrayList<>();
+ do {
parser.nextToken();
- if (parser.ttype == StreamTokenizer.TT_EOF) {
- throw new IOException("Premature end of file, no face has been declared");
+ switch (parser.ttype) {
+ case StreamTokenizer.TT_WORD:
+ if (parser.sval.contains(",") || parser.sval.contains(".")) {
+ valueList.add(Double.valueOf(parser.sval));
+ } else {
+ valueList.add(Integer.valueOf(parser.sval));
+ }
+ break;
+ case StreamTokenizer.TT_EOL:
+ goOn = false;
+ break;
+ default:
+ // the premature end of file is handled elsewhere, keeps the current state
+ parser.pushBack();
+ break;
}
- if (parser.ttype == StreamTokenizer.TT_WORD) {
- z = Double.valueOf(parser.sval).doubleValue();
- parser.nextToken();
- if (parser.ttype == StreamTokenizer.TT_EOF) {
- throw new IOException("Premature end of file, no face has been declared");
- }
- if (parser.ttype == StreamTokenizer.TT_WORD) {
- w = Double.valueOf(parser.sval).doubleValue();
+ } while (goOn);
+ if (minExpectedValueCountPerVertexLine <= valueList.size()) {
+ if (valueList.size() <= maxExpectedValueCountPerVertexLine) {
+ OffImporter.LOGGER.log(Level.INFO, "Coords: "
+ + valueList.stream().map(Number::toString).collect(Collectors.joining(" ")));
+ // TODO put the values into the data store (vertices, normals, colors and/or texture
+ // coordinates)
+ if (offKeyword.getVertexValuesPerTuple() == 3) {
+ store.getDataStore().getVertices().add(new Vector3(valueList.get(0).doubleValue(),
+ valueList.get(1).doubleValue(), valueList.get(2).doubleValue()));
} else {
- w = null;
+ // TODO 4D
+ }
+ int nextIndex = offKeyword.getVertexValuesPerTuple();
+ switch (offKeyword.getNormalValuesPerTuple()) {
+ case 0:
+ // nothing to do
+ break;
+ case 3:
+ store.getDataStore().getNormals()
+ .add(new Vector3(valueList.get(nextIndex).doubleValue(),
+ valueList.get(nextIndex + 1).doubleValue(),
+ valueList.get(nextIndex + 2).doubleValue()));
+ break;
+ case 4:
+ // TODO 4D
+ break;
+ }
+ nextIndex += offKeyword.getNormalValuesPerTuple();
+ final int colorValuesPerTuple = valueList.size() - offKeyword.getVertexValuesPerTuple()
+ - offKeyword.getNormalValuesPerTuple() - offKeyword.getTextureValuesPerTuple();
+ switch (colorValuesPerTuple) {
+ case 0:
+ // nothing to do
+ break;
+ case 1:
+ // TODO store the color map somewhere
+ break;
+ case 3:
+ final ColorRGBA rgb = new ColorRGBA(valueList.get(nextIndex).floatValue(),
+ valueList.get(nextIndex + 1).floatValue(),
+ valueList.get(nextIndex + 2).floatValue(), 0.0f);
+ if (valueList.get(nextIndex) instanceof Integer) {
+ rgb.divideLocal(255.0f);
+ }
+ rgb.setAlpha(1.0f);
+ store.getDataStore().getColors().add(rgb);
+ break;
+ case 4:
+ final ColorRGBA rgba = new ColorRGBA(valueList.get(nextIndex).floatValue(),
+ valueList.get(nextIndex + 1).floatValue(),
+ valueList.get(nextIndex + 2).floatValue(),
+ valueList.get(nextIndex + 3).floatValue());
+ if (valueList.get(nextIndex) instanceof Integer) {
+ rgba.divideLocal(255.0f);
+ }
+ rgba.setAlpha(1.0f);
+ store.getDataStore().getColors().add(rgba);
+ break;
+ }
+ nextIndex += colorValuesPerTuple;
+ if (offKeyword.getTextureValuesPerTuple() == 2) {
+ store.getDataStore().getTextureCoordinates()
+ .add(new Vector2(valueList.get(nextIndex).doubleValue(),
+ valueList.get(nextIndex + 1).doubleValue()));
}
} else {
- z = null;
- w = null;
+ throw new IOException("Too much values per (vertex) line, expected at most "
+ + maxExpectedValueCountPerVertexLine + " values, got " + valueList.size());
+ }
+ } else {
+ throw new IOException("Premature end of (vertex) line, expected at least "
+ + minExpectedValueCountPerVertexLine + " values, got " + valueList.size());
+ }
+ }
+ for (int faceIndex = 0; faceIndex < numberOfFaces.intValue(); faceIndex++) {
+ // skips comment lines, comments and empty lines
+ do {
+ parser.nextToken();
+ } while (parser.ttype != StreamTokenizer.TT_WORD && parser.ttype != StreamTokenizer.TT_EOF);
+ if (parser.ttype == StreamTokenizer.TT_EOF) {
+ throw new IOException("Premature end of file, expected " + numberOfFaces.intValue()
+ + " faces, found " + faceIndex);
+ }
+ // keeps the current word
+ parser.pushBack();
+ // reads as much numbers as possible on the line
+ boolean goOn = true;
+ final List<Number> valueList = new ArrayList<>();
+ do {
+ parser.nextToken();
+ switch (parser.ttype) {
+ case StreamTokenizer.TT_WORD:
+ if (parser.sval.contains(",") || parser.sval.contains(".")) {
+ valueList.add(Double.valueOf(parser.sval));
+ } else {
+ valueList.add(Integer.valueOf(parser.sval));
+ }
+ break;
+ case StreamTokenizer.TT_EOL:
+ goOn = false;
+ break;
+ default:
+ // the premature end of file is handled elsewhere, keeps the current state
+ parser.pushBack();
+ break;
}
+ } while (goOn);
+ if (valueList.isEmpty()) {
+ throw new IOException(
+ "Premature end of (face) line, expected at least one value, got zero");
} else {
- y = null;
- z = null;
- w = null;
+ OffImporter.LOGGER.log(Level.INFO, "Face coords: "
+ + valueList.stream().map(Number::toString).collect(Collectors.joining(" ")));
+ // puts the indices into the geometry store
+ final int vertexIndexCount = valueList.get(0).intValue();
+ final OffFaceInfo faceInfo = new OffFaceInfo();
+ IntStream.rangeClosed(1, vertexIndexCount).mapToObj(valueList::get)
+ .mapToInt(Number::intValue).forEachOrdered(faceInfo::addVertexIndex);
+ final int colorComponentCount = valueList.size() - vertexIndexCount - 1;
+ if (0 < colorComponentCount) {
+ // TODO put the colors into the geometry store
+ }
+ store.addFace(faceInfo);
}
- // TODO support more cases but without ambiguities
- OffImporter.LOGGER.log(Level.INFO, "x: " + x + " y: " + y + " z: " + z + " w: " + w);
}
} catch (final IOException ioe) {
throw new Exception("IO Error on line " + parser.lineno(), ioe);