Commits

Michael Ludwig committed 4cf4051

Implement (but undocumented) geometry loading framework for current geometry interface. Provide an untested PLY ASCII loader.

Comments (0)

Files changed (3)

ferox-renderer/src/main/java/com/ferox/renderer/loader/ASCIIPLYFileLoader.java

+package com.ferox.renderer.loader;
+
+import com.ferox.math.AxisAlignedBox;
+import com.ferox.renderer.ElementBuffer;
+import com.ferox.renderer.Framework;
+import com.ferox.renderer.Renderer;
+import com.ferox.renderer.VertexAttribute;
+import com.ferox.renderer.geom.Geometry;
+import com.ferox.renderer.geom.Tangents;
+import com.ferox.renderer.geom.TriangleIterator;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.*;
+
+/**
+ *
+ */
+public class ASCIIPLYFileLoader implements GeometryFileLoader {
+    @Override
+    public Geometry read(Framework framework, InputStream input) throws IOException {
+        try (BufferedReader in = new BufferedReader(new InputStreamReader(input))) {
+            VertexElementDefinition vertices = null;
+            FaceElementDefinition faces = null;
+
+            List<ElementDefinition> elements = new ArrayList<>();
+            boolean headerDone = false;
+            int currentElement = 0;
+            int elementCount = 0;
+
+            String line = in.readLine();
+            if (!"ply".equalsIgnoreCase(line)) {
+                return null;
+            }
+            if (!"format ascii 1.0".equalsIgnoreCase(line)) {
+                return null;
+            }
+
+            while ((line = in.readLine()) != null) {
+                if (!headerDone) {
+                    // header
+                    if (line.trim().equalsIgnoreCase("end_header")) {
+                        // start elements
+                        headerDone = true;
+                    } else {
+                        String[] parts = line.trim().split("\\s+");
+                        if (parts.length == 3 && parts[0].equalsIgnoreCase("element")) {
+                            if (parts[1].equalsIgnoreCase("vertex")) {
+                                vertices = new VertexElementDefinition(Integer.parseInt(parts[2]));
+                                elements.add(vertices);
+                            } else if (parts[1].equalsIgnoreCase("face")) {
+                                faces = new FaceElementDefinition(Integer.parseInt(parts[2]));
+                                elements.add(faces);
+                            } else {
+                                elements.add(new ElementDefinition(parts[1], Integer.parseInt(parts[2])));
+                            }
+                        } else if (parts.length > 0 && parts[0].equalsIgnoreCase("property")) {
+                            elements.get(elements.size() - 1).addProperty(parts);
+                        }
+                    }
+                } else {
+                    String[] parts = line.trim().split("\\s+");
+                    ElementDefinition e = elements.get(currentElement);
+                    e.processElement(parts, elementCount++);
+                    if (elementCount >= e.count) {
+                        // next element
+                        currentElement++;
+                        elementCount = 0;
+                    }
+                }
+            }
+
+            return new PLYGeometryImpl(framework, vertices, faces);
+        } catch (IndexOutOfBoundsException e) {
+            throw new IOException("Bad element or property definition", e);
+        } catch (NumberFormatException e) {
+            throw new IOException("Bad element entry, unable to parse float value", e);
+        }
+    }
+
+    private static class PLYGeometryImpl implements Geometry {
+        private final VertexAttribute vertices;
+        private final VertexAttribute normals;
+        private final VertexAttribute texCoords;
+        private final VertexAttribute tangents;
+
+        private final ElementBuffer indices;
+
+        private final AxisAlignedBox bounds;
+
+        public PLYGeometryImpl(Framework framework, VertexElementDefinition v, FaceElementDefinition f) {
+            // finalize triangle index list
+            int[] i = new int[f.indices.size()];
+            for (int j = 0; j < i.length; j++) {
+                i[j] = f.indices.get(j);
+            }
+
+            // compute additional vector attributes
+            // FIXME compute normals here as well, if they're not provided
+            TriangleIterator ti = TriangleIterator.Builder.newBuilder().vertices(v.pos).normals(v.norm)
+                                                          .textureCoordinates(v.tc).tangents(v.tan)
+                                                          .fromElements(i, 0, i.length).build();
+            Tangents.compute(ti);
+
+            vertices = new VertexAttribute(framework.newVertexBuffer().from(v.pos).build(), 3);
+            normals = new VertexAttribute(framework.newVertexBuffer().from(v.norm).build(), 3);
+            texCoords = new VertexAttribute(framework.newVertexBuffer().from(v.tc).build(), 2);
+            tangents = new VertexAttribute(framework.newVertexBuffer().from(v.tan).build(), 4);
+
+
+            indices = framework.newElementBuffer().fromUnsigned(i).build();
+            bounds = new AxisAlignedBox(v.pos, 0, 0, v.count);
+        }
+
+        @Override
+        public AxisAlignedBox getBounds() {
+            return bounds;
+        }
+
+        @Override
+        public Renderer.PolygonType getPolygonType() {
+            return Renderer.PolygonType.TRIANGLES;
+        }
+
+        @Override
+        public ElementBuffer getIndices() {
+            return indices;
+        }
+
+        @Override
+        public int getIndexOffset() {
+            return 0;
+        }
+
+        @Override
+        public int getIndexCount() {
+            return indices.getLength();
+        }
+
+        @Override
+        public VertexAttribute getVertices() {
+            return vertices;
+        }
+
+        @Override
+        public VertexAttribute getNormals() {
+            return normals;
+        }
+
+        @Override
+        public VertexAttribute getTextureCoordinates() {
+            return texCoords;
+        }
+
+        @Override
+        public VertexAttribute getTangents() {
+            return tangents;
+        }
+    }
+
+    private static class ElementDefinition {
+        final String name;
+        final int count;
+
+        final Map<String, Integer> properties;
+
+        public ElementDefinition(String name, int count) {
+            this.name = name;
+            this.count = count;
+            properties = new HashMap<>();
+        }
+
+        // assumes line[0] == "property"
+        void addProperty(String[] line) throws IOException {
+            properties.put(line[line.length - 1], properties.size());
+        }
+
+        void processElement(String[] line, int i) {
+            // do nothing in base class, i.e. ignore unknown element types
+        }
+    }
+
+    private static class VertexElementDefinition extends ElementDefinition {
+        private static final List<String> SPECIAL = Arrays.asList("x", "y", "z", "nx", "ny", "nz", "u", "v");
+
+        final float[] pos;
+        final float[] norm;
+        final float[] tc;
+        final float[] tan;
+
+        public VertexElementDefinition(int count) {
+            super("vertex", count);
+            pos = new float[count * 3];
+            norm = new float[count * 3];
+            tc = new float[count * 2];
+            tan = new float[count * 4];
+        }
+
+        boolean hasPosition() {
+            return properties.containsKey("x") && properties.containsKey("y") && properties.containsKey("z");
+        }
+
+        boolean hasNormal() {
+            return properties.containsKey("nx") && properties.containsKey("ny") &&
+                   properties.containsKey("nz");
+        }
+
+        boolean hasTexCoords() {
+            return properties.containsKey("u") && properties.containsKey("v");
+        }
+
+        // TODO add support for vertex colors and the red, green, blue properties
+
+        @Override
+        void addProperty(String[] line) throws IOException {
+            if (SPECIAL.contains(line[line.length - 1])) {
+                // require type to be float
+                if (!line[1].equalsIgnoreCase("float")) {
+                    throw new IOException("Vertex property type other than float is unsupported");
+                }
+            } else if (line[1].equalsIgnoreCase("list")) {
+                // FIXME we could get rid of this restriction if we walked past unknown properties and handled lists correctly
+                throw new IOException("List vertex properties are not supported");
+            }
+            super.addProperty(line);
+        }
+
+        @Override
+        void processElement(String[] line, int i) {
+            if (hasPosition()) {
+                pos[i * 3] = Float.parseFloat(line[properties.get("x")]);
+                pos[i * 3 + 1] = Float.parseFloat(line[properties.get("y")]);
+                pos[i * 3 + 2] = Float.parseFloat(line[properties.get("z")]);
+            }
+            if (hasNormal()) {
+                norm[i * 3] = Float.parseFloat(line[properties.get("nx")]);
+                norm[i * 3 + 1] = Float.parseFloat(line[properties.get("ny")]);
+                norm[i * 3 + 2] = Float.parseFloat(line[properties.get("nz")]);
+            }
+            if (hasTexCoords()) {
+                pos[i * 2] = Float.parseFloat(line[properties.get("u")]);
+                pos[i * 2 + 1] = Float.parseFloat(line[properties.get("v")]);
+            }
+        }
+    }
+
+    private static class FaceElementDefinition extends ElementDefinition {
+        final List<Integer> indices;
+
+        public FaceElementDefinition(int count) {
+            super("face", count);
+            indices = new ArrayList<>();
+        }
+
+        @Override
+        void addProperty(String[] line) throws IOException {
+            if (line[line.length - 1].equalsIgnoreCase("vertex_index")) {
+                if (line.length != 5 || line[1].equalsIgnoreCase("list") ||
+                    !line[2].equalsIgnoreCase("uchar") || !line[3].equalsIgnoreCase("int")) {
+                    throw new IOException("Face vertex_index property must be list uchar int, any other type is unsupported");
+                }
+            }
+            super.addProperty(line);
+        }
+
+        @Override
+        void processElement(String[] line, int i) {
+            // skip if we don't have the conventional vertex_index list defined
+            if (!properties.containsKey("vertex_index")) {
+                return;
+            }
+
+            int base = properties.get("vertex_index");
+            int ct = Integer.parseInt(line[base]);
+            if (ct == 3) {
+                // plain triangle
+                indices.add(Integer.parseInt(line[base + 1]));
+                indices.add(Integer.parseInt(line[base + 2]));
+                indices.add(Integer.parseInt(line[base + 3]));
+            } else if (ct == 4) {
+                // split quad into 2 triangles
+                indices.add(Integer.parseInt(line[base + 1]));
+                indices.add(Integer.parseInt(line[base + 2]));
+                indices.add(Integer.parseInt(line[base + 3]));
+
+                indices.add(Integer.parseInt(line[base + 1]));
+                indices.add(Integer.parseInt(line[base + 3]));
+                indices.add(Integer.parseInt(line[base + 4]));
+            }
+            // else ignore the face format
+        }
+    }
+}

ferox-renderer/src/main/java/com/ferox/renderer/loader/GeometryFileLoader.java

+package com.ferox.renderer.loader;
+
+import com.ferox.renderer.Framework;
+import com.ferox.renderer.geom.Geometry;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ *
+ */
+public interface GeometryFileLoader {
+    public Geometry read(Framework framework, InputStream in) throws IOException;
+}

ferox-renderer/src/main/java/com/ferox/renderer/loader/GeometryLoader.java

+package com.ferox.renderer.loader;
+
+import com.ferox.renderer.Framework;
+import com.ferox.renderer.geom.Geometry;
+
+import java.io.*;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ *
+ */
+public final class GeometryLoader {
+    private static final List<GeometryFileLoader> loaders = new ArrayList<>();
+
+    // register some default loaders
+    static {
+        registerLoader(new ASCIIPLYFileLoader());
+    }
+
+    private GeometryLoader() {
+    }
+
+    /**
+     * <p/>
+     * Register the given loader, so that it can be used in subsequent readGeometry() calls. The newer loaders
+     * are favored when resolving conflicts between loaders that are capable of loading the same file.
+     * <p/>
+     * Does nothing if e is null. If e has already been registered, then e becomes the "newest" with regards
+     * to resolving conflicts.
+     *
+     * @param e A GeometryFileLoader to register for use
+     */
+    public static void registerLoader(GeometryFileLoader e) {
+        synchronized (loaders) {
+            if (e != null) {
+                int index = loaders.indexOf(e);
+                if (index >= 0) {
+                    loaders.remove(index);
+                }
+                loaders.add(e);
+            }
+        }
+    }
+
+    /**
+     * Remove the given loader. Does nothing if it's null or was never registered. After a call to this
+     * method, that loader instance will not be used in calls to readGeometry().
+     *
+     * @param e A GeometryFileLoader that should no longer be used
+     */
+    public static void unregisterLoader(GeometryFileLoader e) {
+        synchronized (loaders) {
+            if (e != null) {
+                loaders.remove(e);
+            }
+        }
+    }
+
+    /**
+     * Read the geometry from the given file, functions identically to readGeometry(stream).
+     *
+     * @param framework The Framework using the created geometry buffers
+     * @param file      The File to read a mesh from
+     *
+     * @return The read mesh
+     *
+     * @throws java.io.IOException if the file can't be read, if it's unsupported, etc.
+     */
+    public static Geometry readGeometry(Framework framework, File file) throws IOException {
+        if (file == null) {
+            throw new IOException("Cannot load a geometry from a null file");
+        }
+
+        try (InputStream stream = new FileInputStream(file)) {
+            return readGeometry(framework, stream);
+        }
+    }
+
+    /**
+     * Read the geometry from the given URL, functions identically to readGeometry(stream).
+     *
+     * @param framework The Framework using the created geometry buffers
+     * @param url       The URL to read a mesh from
+     *
+     * @return The read mesh
+     *
+     * @throws IOException if the url couldn't be read, if it's unsupported or invalid, etc.
+     */
+    public static Geometry readGeometry(Framework framework, URL url) throws IOException {
+        if (url == null) {
+            throw new IOException("Cannot read from a null URL");
+        }
+        try (InputStream urlStream = url.openStream()) {
+            return readGeometry(framework, urlStream);
+        }
+    }
+
+    /**
+     * <p/>
+     * Read a mesh from the given stream. This assumes that the mesh begins with the next bytes read from the
+     * stream, and that the stream is already opened.
+     * <p/>
+     * If the read geometry does not come with normal vectors, smooth normals are computed. The same goes for
+     * tangent vectors.
+     * <p/>
+     * This method does not close the stream, in case it's to be used later on.
+     *
+     * @param framework The Framework using the created geometry buffers
+     * @param stream    The InputStream to read the mesh from
+     *
+     * @return The read geometry
+     *
+     * @throws IOException if the stream can't be read from, it represents an invalid or unsupported mesh file
+     *                     type, etc.
+     */
+    public static Geometry readGeometry(Framework framework, InputStream stream) throws IOException {
+        // make sure we're buffered
+        if (!(stream instanceof BufferedInputStream)) {
+            stream = new BufferedInputStream(stream);
+        }
+
+        // load the file
+        Geometry t;
+
+        synchronized (loaders) {
+            for (int i = loaders.size() - 1; i >= 0; i--) {
+                stream.reset();
+                t = loaders.get(i).read(framework, stream);
+                if (t != null) {
+                    return t; // we've loaded it
+                }
+            }
+        }
+
+        throw new IOException("Unable to load the given geometry, no registered loader with support");
+    }
+}