diff options
author | Julien Gouesse <[email protected]> | 2023-03-06 01:11:52 +0100 |
---|---|---|
committer | Julien Gouesse <[email protected]> | 2023-03-06 01:11:52 +0100 |
commit | d8da0b55b161873e7bee159d4d9e5f4dec510270 (patch) | |
tree | 3cd5372f4642a8ebef4abac6e8192ad0086181d0 | |
parent | c8cea1d355ab81acd887ab014d3727ef875b8b71 (diff) |
Adds the very first (unfinished, work in progress) blueprint of the OFF importer
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(); + } +} |