aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJulien Gouesse <[email protected]>2023-03-06 01:11:52 +0100
committerJulien Gouesse <[email protected]>2023-03-06 01:11:52 +0100
commitd8da0b55b161873e7bee159d4d9e5f4dec510270 (patch)
tree3cd5372f4642a8ebef4abac6e8192ad0086181d0
parentc8cea1d355ab81acd887ab014d3727ef875b8b71 (diff)
Adds the very first (unfinished, work in progress) blueprint of the OFF importer
-rw-r--r--ardor3d-examples/src/main/java/com/ardor3d/example/pipeline/SimpleOffExample.java47
-rw-r--r--ardor3d-examples/src/main/resources/com/ardor3d/example/i18n/example_descriptions.properties2
-rw-r--r--ardor3d-extras/src/main/java/com/ardor3d/extension/model/off/OffDataStore.java50
-rw-r--r--ardor3d-extras/src/main/java/com/ardor3d/extension/model/off/OffEdgeInfo.java50
-rw-r--r--ardor3d-extras/src/main/java/com/ardor3d/extension/model/off/OffFaceInfo.java60
-rw-r--r--ardor3d-extras/src/main/java/com/ardor3d/extension/model/off/OffGeometryStore.java314
-rw-r--r--ardor3d-extras/src/main/java/com/ardor3d/extension/model/off/OffImporter.java368
-rw-r--r--ardor3d-extras/src/main/java/com/ardor3d/extension/model/off/OffMaterial.java20
8 files changed, 911 insertions, 0 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
new file mode 100644
index 0000000..c8b3f19
--- /dev/null
+++ b/ardor3d-examples/src/main/java/com/ardor3d/example/pipeline/SimpleOffExample.java
@@ -0,0 +1,47 @@
+/**
+ * Copyright (c) 2008-2023 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.example.pipeline;
+
+import com.ardor3d.example.ExampleBase;
+import com.ardor3d.example.Purpose;
+import com.ardor3d.extension.model.off.OffGeometryStore;
+import com.ardor3d.extension.model.off.OffImporter;
+import com.ardor3d.math.Vector3;
+import com.ardor3d.scenegraph.Node;
+
+/**
+ * Simplest example of loading a OFF model.
+ */
+@Purpose(htmlDescriptionKey = "com.ardor3d.example.pipeline.SimpleOffExample", //
+ thumbnailPath = "com/ardor3d/example/media/thumbnails/pipeline_SimpleOffExample.jpg", //
+ maxHeapMemory = 64)
+public class SimpleOffExample extends ExampleBase {
+ public static void main(final String[] args) {
+ ExampleBase.start(SimpleOffExample.class);
+ }
+
+ @Override
+ protected void initExample() {
+ _canvas.setTitle("Ardor3D - Simple Off Example");
+ _canvas.getCanvasRenderer().getCamera().setLocation(new Vector3(3.5, 1.5, 5));
+
+ // Load the OFF scene
+ final long time = System.currentTimeMillis();
+ final OffImporter importer = new OffImporter();
+ final OffGeometryStore storage = importer.load("off/cube.off");
+ System.out.println("Importing Took " + (System.currentTimeMillis() - time) + " ms");
+
+ final Node model = storage.getScene();
+ // the off model is usually z-up - switch to y-up, isn't it?
+ // model.setRotation(new Quaternion().fromAngleAxis(-MathUtils.HALF_PI, Vector3.UNIT_X));
+ _root.attachChild(model);
+ }
+}
diff --git a/ardor3d-examples/src/main/resources/com/ardor3d/example/i18n/example_descriptions.properties b/ardor3d-examples/src/main/resources/com/ardor3d/example/i18n/example_descriptions.properties
index 646a251..dda76ae 100644
--- a/ardor3d-examples/src/main/resources/com/ardor3d/example/i18n/example_descriptions.properties
+++ b/ardor3d-examples/src/main/resources/com/ardor3d/example/i18n/example_descriptions.properties
@@ -50,6 +50,8 @@ com.ardor3d.example.pipeline.SimpleColladaExample=Simplest example of loading a
com.ardor3d.example.pipeline.SimpleMd2Example=Simplest example of loading a model in MD2 format.
com.ardor3d.example.pipeline.SimpleMd3Example=Simplest example of loading a model in MD3 format.
com.ardor3d.example.pipeline.SimpleObjExample=Simplest example of loading a Wavefront OBJ model.
+com.ardor3d.example.pipeline.SimpleOffExample=Simplest example of loading an OFF model.
+com.ardor3d.example.pipeline.SimplePlyExample=Simplest example of loading a PLY model.
com.ardor3d.example.renderer.BillboardNodeExample=Illustrates the BillboardNode class; which defines a node that always orients towards the camera.
com.ardor3d.example.renderer.BillboardNodeZExample=Illustrates the BillboardNode class - but using a Z-up camera.
com.ardor3d.example.renderer.ClipStateExample=Illustrates the ClipState class; which specifies a plane to test for clipping of a Node.
diff --git a/ardor3d-extras/src/main/java/com/ardor3d/extension/model/off/OffDataStore.java b/ardor3d-extras/src/main/java/com/ardor3d/extension/model/off/OffDataStore.java
new file mode 100644
index 0000000..5d1a0e3
--- /dev/null
+++ b/ardor3d-extras/src/main/java/com/ardor3d/extension/model/off/OffDataStore.java
@@ -0,0 +1,50 @@
+/**
+ * Copyright (c) 2008-2014 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.extension.model.off;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import com.ardor3d.math.ColorRGBA;
+import com.ardor3d.math.Vector2;
+import com.ardor3d.math.Vector3;
+
+public class OffDataStore {
+
+ private final List<Vector3> _vertices;
+ private final List<Vector3> _normals;
+ private final List<ColorRGBA> _colors;
+ private final List<Vector2> _textureCoordinates;
+
+ public OffDataStore() {
+ super();
+ _vertices = new ArrayList<>();
+ _normals = new ArrayList<>();
+ _colors = new ArrayList<>();
+ _textureCoordinates = new ArrayList<>();
+ }
+
+ public List<Vector3> getVertices() {
+ return _vertices;
+ }
+
+ public List<Vector3> getNormals() {
+ return _normals;
+ }
+
+ public List<ColorRGBA> getColors() {
+ return _colors;
+ }
+
+ public List<Vector2> getTextureCoordinates() {
+ return _textureCoordinates;
+ }
+}
diff --git a/ardor3d-extras/src/main/java/com/ardor3d/extension/model/off/OffEdgeInfo.java b/ardor3d-extras/src/main/java/com/ardor3d/extension/model/off/OffEdgeInfo.java
new file mode 100644
index 0000000..b65ec67
--- /dev/null
+++ b/ardor3d-extras/src/main/java/com/ardor3d/extension/model/off/OffEdgeInfo.java
@@ -0,0 +1,50 @@
+/**
+ * Copyright (c) 2008-2014 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.extension.model.off;
+
+import com.ardor3d.math.ColorRGBA;
+
+public class OffEdgeInfo {
+
+ private Integer index1;
+
+ private Integer index2;
+
+ private ColorRGBA color;
+
+ public OffEdgeInfo() {
+ super();
+ }
+
+ public Integer getIndex1() {
+ return index1;
+ }
+
+ public void setIndex1(final Integer index1) {
+ this.index1 = index1;
+ }
+
+ public Integer getIndex2() {
+ return index2;
+ }
+
+ public void setIndex2(final Integer index2) {
+ this.index2 = index2;
+ }
+
+ public ColorRGBA getColor() {
+ return color;
+ }
+
+ public void setColor(final ColorRGBA color) {
+ this.color = color;
+ }
+}
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
new file mode 100644
index 0000000..d0cec71
--- /dev/null
+++ b/ardor3d-extras/src/main/java/com/ardor3d/extension/model/off/OffFaceInfo.java
@@ -0,0 +1,60 @@
+/**
+ * Copyright (c) 2008-2014 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.extension.model.off;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class OffFaceInfo {
+
+ private List<Integer> _vertexIndices;
+
+ private List<Integer> _materialIndices;
+
+ private List<Float> _textureCoordinates;
+
+ public OffFaceInfo() {
+ super();
+ }
+
+ public void addVertexIndex(final int vertexIndex) {
+ if (_vertexIndices == null) {
+ _vertexIndices = new ArrayList<>();
+ }
+ _vertexIndices.add(Integer.valueOf(vertexIndex));
+ }
+
+ public List<Integer> getVertexIndices() {
+ return _vertexIndices;
+ }
+
+ public void addMaterialIndex(final int materialIndex) {
+ if (_materialIndices == null) {
+ _materialIndices = new ArrayList<>();
+ }
+ _materialIndices.add(Integer.valueOf(materialIndex));
+ }
+
+ public List<Integer> getMaterialIndices() {
+ return _materialIndices;
+ }
+
+ public void addTextureCoordinate(final float textureCoordinate) {
+ if (_textureCoordinates == null) {
+ _textureCoordinates = new ArrayList<>();
+ }
+ _textureCoordinates.add(Float.valueOf(textureCoordinate));
+ }
+
+ public List<Float> getTextureCoordinates() {
+ return _textureCoordinates;
+ }
+}
diff --git a/ardor3d-extras/src/main/java/com/ardor3d/extension/model/off/OffGeometryStore.java b/ardor3d-extras/src/main/java/com/ardor3d/extension/model/off/OffGeometryStore.java
new file mode 100644
index 0000000..dce55a7
--- /dev/null
+++ b/ardor3d-extras/src/main/java/com/ardor3d/extension/model/off/OffGeometryStore.java
@@ -0,0 +1,314 @@
+/**
+ * Copyright (c) 2008-2014 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.extension.model.off;
+
+import java.nio.Buffer;
+import java.nio.FloatBuffer;
+import java.util.ArrayList;
+import java.util.EnumSet;
+import java.util.List;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import com.ardor3d.image.Texture;
+import com.ardor3d.math.ColorRGBA;
+import com.ardor3d.math.Vector2;
+import com.ardor3d.math.Vector3;
+import com.ardor3d.math.type.ReadOnlyColorRGBA;
+import com.ardor3d.renderer.IndexMode;
+import com.ardor3d.renderer.state.TextureState;
+import com.ardor3d.scenegraph.IndexBufferData;
+import com.ardor3d.scenegraph.Line;
+import com.ardor3d.scenegraph.Mesh;
+import com.ardor3d.scenegraph.Node;
+import com.ardor3d.util.geom.BufferUtils;
+import com.ardor3d.util.geom.GeometryTool;
+import com.ardor3d.util.geom.GeometryTool.MatchCondition;
+
+public class OffGeometryStore {
+
+ private static final Logger LOGGER = Logger.getLogger(OffGeometryStore.class.getName());
+
+ private int _totalMeshes = 0;
+
+ private int _totalLines = 0;
+
+ private final OffDataStore _dataStore;
+
+ private final Node _root;
+
+ private final List<OffMaterial> _materialLibrary;
+
+ private List<OffFaceInfo> _offFaceInfoList;
+
+ private List<OffEdgeInfo> _offEdgeInfoList;
+
+ private Texture _texture;
+
+ private String _textureName;
+
+ private final GeometryTool _geometryTool;
+
+ public OffGeometryStore() {
+ this(new GeometryTool());
+ }
+
+ public OffGeometryStore(final GeometryTool geometryTool) {
+ super();
+ _dataStore = new OffDataStore();
+ _root = new Node();
+ _materialLibrary = new ArrayList<>();
+ _geometryTool = geometryTool;
+ }
+
+ public OffDataStore getDataStore() {
+ return _dataStore;
+ }
+
+ public Node getScene() {
+ return _root;
+ }
+
+ public List<OffMaterial> getMaterialLibrary() {
+ return _materialLibrary;
+ }
+
+ public String getTextureName() {
+ return _textureName;
+ }
+
+ public void setTextureName(final String textureName) {
+ _textureName = textureName;
+ }
+
+ public Texture getTexture() {
+ return _texture;
+ }
+
+ public void setTexture(final Texture texture) {
+ _texture = texture;
+ }
+
+ public TextureState getTextureState() {
+ if (_texture != null) {
+ final TextureState tState = new TextureState();
+ tState.setTexture(_texture, 0);
+ return tState;
+ }
+ return null;
+ }
+
+ void addLine(final OffEdgeInfo edgeInfo) {
+ if (_offEdgeInfoList == null) {
+ _offEdgeInfoList = new ArrayList<>();
+ }
+ _offEdgeInfoList.add(edgeInfo);
+ }
+
+ void addFace(final OffFaceInfo faceInfo) {
+ if (_offFaceInfoList == null) {
+ _offFaceInfoList = new ArrayList<>();
+ }
+ _offFaceInfoList.add(faceInfo);
+ }
+
+ @SuppressWarnings("null")
+ void commitObjects() {
+ if (_offEdgeInfoList != null) {
+ final String name = "off_line" + _totalLines;
+ boolean hasColors = false;
+ final boolean hasNormals = _dataStore.getNormals() != null && !_dataStore.getNormals().isEmpty();
+ final int vertexCount = _offEdgeInfoList.size() * 2;
+ final Vector3[] vertices = new Vector3[vertexCount];
+ final Vector3[] normals = hasNormals ? null : new Vector3[vertexCount];
+ ReadOnlyColorRGBA[] colors = null;
+ final IndexBufferData<? extends Buffer> indices = BufferUtils.createIndexBufferData(vertexCount,
+ vertexCount - 1);
+ int edgeVertexIndex = 0;
+ for (final OffEdgeInfo offEdgeInfo : _offEdgeInfoList) {
+ indices.put(edgeVertexIndex).put(edgeVertexIndex + 1);
+ vertices[edgeVertexIndex] = _dataStore.getVertices().get(offEdgeInfo.getIndex1());
+ vertices[edgeVertexIndex + 1] = _dataStore.getVertices().get(offEdgeInfo.getIndex2());
+ if (hasNormals) {
+ normals[edgeVertexIndex] = _dataStore.getNormals().get(offEdgeInfo.getIndex1());
+ normals[edgeVertexIndex + 1] = _dataStore.getNormals().get(offEdgeInfo.getIndex2());
+ }
+ if (offEdgeInfo.getColor() != null) {
+ if (colors == null) {
+ colors = new ReadOnlyColorRGBA[vertexCount];
+ }
+ colors[edgeVertexIndex] = offEdgeInfo.getColor();
+ colors[edgeVertexIndex + 1] = offEdgeInfo.getColor();
+ hasColors = true;
+ }
+ edgeVertexIndex += 2;
+ }
+ final Line line = new Line(name, vertices, normals, colors, null);
+ indices.rewind();
+ line.getMeshData().setIndices(indices);
+ final EnumSet<MatchCondition> matchConditions = EnumSet.noneOf(MatchCondition.class);
+ if (hasNormals) {
+ matchConditions.add(MatchCondition.Normal);
+ }
+ if (hasColors) {
+ matchConditions.add(MatchCondition.Color);
+ }
+ _geometryTool.minimizeVerts(line, matchConditions);
+
+ final TextureState tState = getTextureState();
+ if (tState != null) {
+ line.setRenderState(tState);
+ }
+
+ line.updateModelBound();
+ _totalLines++;
+ _offEdgeInfoList = null;
+ }
+ if (_offFaceInfoList != null) {
+ final String name = "off_mesh" + _totalMeshes;
+ final Mesh mesh = new Mesh(name);
+ boolean hasTexCoordsInFaces = false;
+ final boolean hasTexCoordsInVertices = _dataStore.getTextureCoordinates() != null
+ && !_dataStore.getTextureCoordinates().isEmpty();
+ final boolean hasNormals = _dataStore.getNormals() != null && !_dataStore.getNormals().isEmpty();
+ final boolean hasColors = _dataStore.getColors() != null && !_dataStore.getColors().isEmpty();
+ int vertexCount = 0;
+ for (final OffFaceInfo offFaceInfo : _offFaceInfoList) {
+ vertexCount += offFaceInfo.getVertexIndices().size();
+ if (offFaceInfo.getTextureCoordinates() != null && !offFaceInfo.getTextureCoordinates().isEmpty()) {
+ hasTexCoordsInFaces = true;
+ }
+ }
+ final FloatBuffer vertices = BufferUtils.createVector3Buffer(vertexCount);
+ final IndexBufferData<? extends Buffer> indices = BufferUtils.createIndexBufferData(vertexCount,
+ vertexCount - 1);
+
+ final FloatBuffer normals = hasNormals ? BufferUtils.createFloatBuffer(vertices.capacity()) : null;
+ final FloatBuffer colors = hasColors ? BufferUtils.createFloatBuffer(vertexCount * 4) : null;
+ final FloatBuffer uvs = hasTexCoordsInFaces || hasTexCoordsInVertices
+ ? BufferUtils.createFloatBuffer(vertexCount * 2)
+ : null;
+
+ int dummyVertexIndex = 0;
+ final List<IndexMode> indexModeList = new ArrayList<>();
+ final List<Integer> indexLengthList = new ArrayList<>();
+ for (final OffFaceInfo offFaceInfo : _offFaceInfoList) {
+ final IndexMode previousIndexMode = indexModeList.isEmpty() ? null
+ : indexModeList.get(indexModeList.size() - 1);
+ final IndexMode currentIndexMode;
+ switch (offFaceInfo.getVertexIndices().size()) {
+ case 3: {
+ currentIndexMode = IndexMode.Triangles;
+ break;
+ }
+ case 4: {
+ currentIndexMode = IndexMode.Quads;
+ break;
+ }
+ default: {
+ currentIndexMode = null;
+ break;
+ }
+ }
+ if (currentIndexMode == null) {
+ OffGeometryStore.LOGGER.log(Level.SEVERE,
+ "The index mode cannot be determined for a face containing "
+ + offFaceInfo.getVertexIndices().size() + " vertices");
+ } else {
+ if (previousIndexMode == null || currentIndexMode != previousIndexMode) {
+ indexModeList.add(currentIndexMode);
+ indexLengthList.add(currentIndexMode.getVertexCount());
+ } else {
+ final int previousIndexLength = indexLengthList.get(indexLengthList.size() - 1).intValue();
+ final int currentIndexLength = previousIndexLength + currentIndexMode.getVertexCount();
+ indexLengthList.set(indexLengthList.size() - 1, Integer.valueOf(currentIndexLength));
+ }
+ for (final Integer vertexIndex : offFaceInfo.getVertexIndices()) {
+ indices.put(dummyVertexIndex);
+ final Vector3 vertex = _dataStore.getVertices().get(vertexIndex.intValue());
+ vertices.put(vertex.getXf()).put(vertex.getYf()).put(vertex.getZf());
+ if (hasNormals) {
+ final Vector3 normal = _dataStore.getNormals().get(vertexIndex.intValue());
+ normals.put(normal.getXf()).put(normal.getYf()).put(normal.getZf());
+ }
+ if (hasColors) {
+ final ColorRGBA color = _dataStore.getColors().get(vertexIndex.intValue());
+ colors.put(color.getRed()).put(color.getGreen()).put(color.getBlue()).put(color.getAlpha());
+ }
+ if (hasTexCoordsInVertices) {
+ final Vector2 texCoords = _dataStore.getTextureCoordinates().get(vertexIndex.intValue());
+ uvs.put(texCoords.getXf()).put(texCoords.getYf());
+ }
+ dummyVertexIndex++;
+ }
+ if (hasTexCoordsInFaces) {
+ for (final Float texCoord : offFaceInfo.getTextureCoordinates()) {
+ uvs.put(texCoord);
+ }
+ }
+ }
+ }
+
+ vertices.rewind();
+ mesh.getMeshData().setVertexBuffer(vertices);
+ indices.rewind();
+ mesh.getMeshData().setIndices(indices);
+ if (indexModeList.size() == 1) {
+ mesh.getMeshData().setIndexMode(indexModeList.get(0));
+ mesh.getMeshData().setIndexLengths(null);
+ } else {
+ mesh.getMeshData().setIndexModes(indexModeList.toArray(new IndexMode[indexModeList.size()]));
+ final int[] indexLengths = new int[indexLengthList.size()];
+ for (int indexLengthIndex = 0; indexLengthIndex < indexLengths.length; indexLengthIndex++) {
+ indexLengths[indexLengthIndex] = indexLengthList.get(indexLengthIndex).intValue();
+ }
+ mesh.getMeshData().setIndexLengths(indexLengths);
+ }
+ final EnumSet<MatchCondition> matchConditions = EnumSet.noneOf(MatchCondition.class);
+ if (hasNormals) {
+ normals.rewind();
+ mesh.getMeshData().setNormalBuffer(normals);
+ matchConditions.add(MatchCondition.Normal);
+ }
+ if (hasColors) {
+ colors.rewind();
+ mesh.getMeshData().setColorBuffer(colors);
+ matchConditions.add(MatchCondition.Color);
+ }
+ if (hasTexCoordsInFaces || hasTexCoordsInVertices) {
+ uvs.rewind();
+ mesh.getMeshData().setTextureBuffer(uvs, 0);
+ matchConditions.add(MatchCondition.UVs);
+ }
+
+ if (indexModeList.size() == 1) {
+ _geometryTool.minimizeVerts(mesh, matchConditions);
+ } else {
+ // FIXME unsure about minimizeVerts preserving the index modes
+ }
+
+ final TextureState tState = getTextureState();
+ if (tState != null) {
+ mesh.setRenderState(tState);
+ }
+
+ mesh.updateModelBound();
+ _root.attachChild(mesh);
+ _totalMeshes++;
+ _offFaceInfoList = null;
+ }
+ }
+
+ void cleanup() {
+ _offFaceInfoList = null;
+ _offEdgeInfoList = null;
+ }
+}
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
new file mode 100644
index 0000000..b63a3b0
--- /dev/null
+++ b/ardor3d-extras/src/main/java/com/ardor3d/extension/model/off/OffImporter.java
@@ -0,0 +1,368 @@
+/**
+ * Copyright (c) 2008-2023 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.extension.model.off;
+
+import java.io.BufferedReader;
+import java.io.Closeable;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.io.StreamTokenizer;
+import java.nio.charset.StandardCharsets;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import java.util.stream.Stream;
+
+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>
+ *
+ * N.B: WORK IN PROGRESS, STILL A LOT OF WORK TO DO
+ *
+ * N.B: supports only the ASCII file format as there's a lack of available binary files to make some tests and the
+ * specification mentions some constants in the include file named "off.h" but it's not included in Geomview's source
+ * code
+ */
+public class OffImporter {
+
+// public static interface OffReader extends Closeable {
+// public double read() throws IOException;
+// }
+
+// public static class AsciiOffReader implements OffReader {
+//
+// private final OffFileParser parser;
+// private final BufferedReader reader;
+//
+// public AsciiOffReader(final InputStream stream) {
+// super();
+// reader = new BufferedReader(new InputStreamReader(stream, StandardCharsets.US_ASCII));
+// parser = new OffFileParser(reader);
+// }
+//
+// @Override
+// public double read() throws IOException {
+// do {
+// parser.nextToken();
+// } while (parser.ttype != StreamTokenizer.TT_WORD && parser.ttype != StreamTokenizer.TT_EOF);
+// if (parser.ttype == StreamTokenizer.TT_WORD) {
+// try {
+// parser.nval = Double.valueOf(parser.sval).doubleValue();
+// return parser.nval;
+// } catch (final NumberFormatException nbe) {
+// throw new IOException("Unparsable string " + parser.sval, nbe);
+// }
+// } else {
+// throw new IOException("No number to read, end of file reached");
+// }
+// }
+//
+// @Override
+// public void close() throws IOException {
+// reader.close();
+// }
+// }
+
+ private static final Logger LOGGER = Logger.getLogger(OffImporter.class.getName());
+
+ public static class OffFileParser extends StreamTokenizer implements Closeable {
+
+ private final Reader reader;
+
+ /**
+ * Constructor.
+ *
+ * @param reader
+ * The Reader.
+ */
+ public OffFileParser(final Reader reader) {
+ super(reader);
+ this.reader = reader;
+ resetSyntax();
+ eolIsSignificant(true);
+ lowerCaseMode(true);
+
+ // all printable ascii characters
+ wordChars('!', '~');
+
+ whitespaceChars(' ', ' ');
+ whitespaceChars('\n', '\n');
+ whitespaceChars('\r', '\r');
+ whitespaceChars('\t', '\t');
+
+ commentChar('#');
+ }
+
+ /**
+ * Gets a number from the stream. Need to extract numbers since they may be in scientific notation. The number
+ * is returned in nval.
+ *
+ * @return Logical-true if successful, else logical-false.
+ */
+// protected boolean getNumber() {
+// try {
+// nextToken();
+// if (ttype != StreamTokenizer.TT_WORD) {
+// return false;
+// }
+// nval = Double.valueOf(sval).doubleValue();
+// } catch (final IOException e) {
+// System.err.println(e.getMessage());
+// return false;
+// } catch (final NumberFormatException e) {
+// System.err.println(e.getMessage());
+// return false;
+// }
+// return true;
+// }
+
+ @Override
+ public void close() throws IOException {
+ reader.close();
+ }
+ }
+
+ private ResourceLocator _modelLocator;
+
+ /**
+ * Constructor.
+ */
+ public OffImporter() {
+ super();
+ }
+
+ /**
+ * Reads a OFF file from the given resource
+ *
+ * @param resource
+ * the name of the resource to find.
+ *
+ * @return a OffGeometryStore data object containing the scene and other useful elements.
+ */
+ public OffGeometryStore load(final String resource) {
+ return load(resource, new GeometryTool());
+ }
+
+ /**
+ * Reads a OFF file from the given resource
+ *
+ * @param resource
+ * the name of the resource to find.
+ * @param geometryTool
+ * the geometry tool to optimize the meshes
+ *
+ * @return a OffGeometryStore data object containing the scene and other useful elements.
+ */
+ public OffGeometryStore load(final String resource, final GeometryTool geometryTool) {
+ final ResourceSource source;
+ if (_modelLocator == null) {
+ source = ResourceLocatorTool.locateResource(ResourceLocatorTool.TYPE_MODEL, resource);
+ } else {
+ source = _modelLocator.locateResource(resource);
+ }
+
+ if (source == null) {
+ throw new Error("Unable to locate '" + resource + "'");
+ }
+
+ return load(source, geometryTool);
+ }
+
+ /**
+ * Reads a OFF file from the given resource
+ *
+ * @param resource
+ * the resource to find.
+ *
+ * @return a OffGeometryStore data object containing the scene and other useful elements.
+ */
+ public OffGeometryStore load(final ResourceSource resource) {
+ return load(resource, new GeometryTool());
+ }
+
+ /**
+ * Reads a OFF file from the given resource
+ *
+ * @param resource
+ * the resource to find.
+ * @param geometryTool
+ * the geometry tool to optimize the meshes
+ *
+ * @return a OffGeometryStore data object containing the scene and other useful elements.
+ */
+ @SuppressWarnings("resource")
+ public OffGeometryStore load(final ResourceSource resource, final GeometryTool geometryTool) {
+ final OffGeometryStore store = createGeometryStore(geometryTool);
+ try {
+ try (final OffFileParser parser = new OffFileParser(
+ new BufferedReader(new InputStreamReader(resource.openStream(), StandardCharsets.US_ASCII)))) {
+ try {
+ final Integer numberOfVertices;
+ Integer numberOfFaces = null;
+ Integer numberOfEdges = null;
+ // starts reading the file as ascii (binary file not supported)
+ // skips the commented line(s)
+ do {
+ parser.nextToken();
+ } while (parser.ttype != StreamTokenizer.TT_WORD && parser.ttype != StreamTokenizer.TT_EOF);
+ // if the end of file is reached too early
+ if (parser.ttype == StreamTokenizer.TT_EOF) {
+ throw new IOException(
+ "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 {
+ // no *off keyword
+ OffImporter.LOGGER.log(Level.INFO, "No off keyword on line " + parser.lineno());
+ unhandledFirstParsedValue = parser.sval;
+ }
+ parser.nextToken();
+ if (parser.ttype == StreamTokenizer.TT_EOF) {
+ throw new IOException(
+ "Premature end of file, expected an optional off keyword followed by three integers vertex_count face_count edge_count");
+ }
+ if (parser.ttype == StreamTokenizer.TT_EOL) {
+ if (unhandledFirstParsedValue == null) {
+ // reads the 3 integer values on the second uncommented line, most common scenario:
+ // *OFF
+ // # comment
+ // # another comment
+ // # etc
+ // vertex_count face_count edge_count
+ // skips the commented line(s)
+ do {
+ parser.nextToken();
+ } while (parser.ttype != StreamTokenizer.TT_WORD && parser.ttype != StreamTokenizer.TT_EOF);
+ // if the end of file is reached too early
+ if (parser.ttype == StreamTokenizer.TT_EOF) {
+ throw new IOException(
+ "Premature end of line, expected three integers vertex_count face_count edge_count");
+ } else {
+ numberOfVertices = Integer.valueOf(parser.sval);
+ parser.nextToken();
+ }
+ } else {
+ throw new IOException(
+ "Premature end of line, expected three integers vertex_count face_count edge_count");
+ }
+ } else {
+ if (unhandledFirstParsedValue == null) {
+ // *off keyword and three integers on the first line, rare scenario:
+ // *OFF vertex_count face_count edge_count
+ // reads the 3 integer values on the first uncommented line, most common scenario
+ numberOfVertices = Integer.valueOf(parser.sval);
+ parser.nextToken();
+ } else {
+ // no *off keyword, three integers on the first line, very improbable scenario:
+ // vertex_count face_count edge_count
+ // read the 2 next integer values on the first line
+ numberOfVertices = Integer.valueOf(unhandledFirstParsedValue);
+ }
+ }
+ if (parser.ttype == StreamTokenizer.TT_WORD) {
+ numberOfFaces = Integer.valueOf(parser.sval);
+ } else {
+ throw new IOException(
+ "Premature end of line, expected three integers vertex_count face_count edge_count");
+ }
+ parser.nextToken();
+ if (parser.ttype == StreamTokenizer.TT_WORD) {
+ numberOfEdges = Integer.valueOf(parser.sval);
+ } else {
+ throw new IOException(
+ "Premature end of line, expected three integers vertex_count face_count edge_count");
+ }
+ // reads until the end of line is reached (expected)
+ do {
+ parser.nextToken();
+ } while (parser.ttype != StreamTokenizer.TT_WORD && parser.ttype != StreamTokenizer.TT_EOF
+ && parser.ttype != StreamTokenizer.TT_EOL);
+ if (parser.ttype == StreamTokenizer.TT_WORD) {
+ throw new IOException("Unexpected word " + parser.sval);
+ }
+ 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
+ for (int vertexIndex = 0; vertexIndex < numberOfVertices.intValue(); vertexIndex++) {
+ 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();
+ if (parser.ttype == StreamTokenizer.TT_EOF) {
+ throw new IOException("Premature end of file, no face has been declared");
+ }
+ if (parser.ttype == StreamTokenizer.TT_WORD) {
+ y = 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) {
+ 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();
+ } else {
+ w = null;
+ }
+ } else {
+ z = null;
+ w = null;
+ }
+ } else {
+ y = null;
+ z = null;
+ w = null;
+ }
+ // 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);
+ }
+ }
+ } catch (final Throwable t) {
+ throw new Error("Unable to load off resource from URL: " + resource, t);
+ }
+ store.commitObjects();
+ store.cleanup();
+ return store;
+ }
+
+ protected OffGeometryStore createGeometryStore(final GeometryTool geometryTool) {
+ return new OffGeometryStore(geometryTool);
+ }
+
+ public void setModelLocator(final ResourceLocator locator) {
+ _modelLocator = locator;
+ }
+}
diff --git a/ardor3d-extras/src/main/java/com/ardor3d/extension/model/off/OffMaterial.java b/ardor3d-extras/src/main/java/com/ardor3d/extension/model/off/OffMaterial.java
new file mode 100644
index 0000000..1f67270
--- /dev/null
+++ b/ardor3d-extras/src/main/java/com/ardor3d/extension/model/off/OffMaterial.java
@@ -0,0 +1,20 @@
+/**
+ * Copyright (c) 2008-2014 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.extension.model.off;
+
+import com.ardor3d.extension.model.util.AbstractMaterial;
+
+public class OffMaterial extends AbstractMaterial {
+
+ public OffMaterial() {
+ super();
+ }
+}