| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package org.apache.commons.geometry.euclidean.threed; |
| |
| import java.io.BufferedReader; |
| import java.io.EOFException; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.InputStreamReader; |
| import java.text.ParseException; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.List; |
| import java.util.StringTokenizer; |
| |
| import org.apache.commons.numbers.core.Precision; |
| |
| /** This class is a small and incomplete parser for PLY files. |
| * <p> |
| * This parser is only intended for test purposes, it does not |
| * parse the full header, it does not handle all properties, |
| * it has rudimentary error handling. |
| * </p> |
| */ |
| public class PLYParser { |
| |
| /** Parsed vertices. */ |
| private Vector3D[] vertices; |
| |
| /** Parsed faces. */ |
| private int[][] faces; |
| |
| /** Reader for PLY data. */ |
| private BufferedReader br; |
| |
| /** Last parsed line. */ |
| private String line; |
| |
| /** Simple constructor. |
| * @param stream stream to parse (closing it remains caller responsibility) |
| * @exception IOException if stream cannot be read |
| * @exception ParseException if stream content cannot be parsed |
| */ |
| public PLYParser(final InputStream stream) |
| throws IOException, ParseException { |
| |
| try { |
| br = new BufferedReader(new InputStreamReader(stream, "UTF-8")); |
| |
| // parse the header |
| List<Field> fields = parseNextLine(); |
| if (fields.size() != 1 || fields.get(0).getToken() != Token.PLY) { |
| complain(); |
| } |
| |
| boolean parsing = true; |
| int nbVertices = -1; |
| int nbFaces = -1; |
| int xIndex = -1; |
| int yIndex = -1; |
| int zIndex = -1; |
| int vPropertiesNumber = -1; |
| boolean inVertexElt = false; |
| boolean inFaceElt = false; |
| while (parsing) { |
| fields = parseNextLine(); |
| if (fields.size() < 1) { |
| complain(); |
| } |
| switch (fields.get(0).getToken()) { |
| case FORMAT: |
| if (fields.size() != 3 || |
| fields.get(1).getToken() != Token.ASCII || |
| fields.get(2).getToken() != Token.UNKNOWN || |
| !Precision.equals(Double.parseDouble(fields.get(2).getValue()), 1.0, 0.001)) { |
| complain(); |
| } |
| inVertexElt = false; |
| inFaceElt = false; |
| break; |
| case COMMENT: |
| // we just ignore this line |
| break; |
| case ELEMENT: |
| if (fields.size() != 3 || |
| (fields.get(1).getToken() != Token.VERTEX && fields.get(1).getToken() != Token.FACE) || |
| fields.get(2).getToken() != Token.UNKNOWN) { |
| complain(); |
| } |
| if (fields.get(1).getToken() == Token.VERTEX) { |
| nbVertices = Integer.parseInt(fields.get(2).getValue()); |
| inVertexElt = true; |
| inFaceElt = false; |
| } else { |
| nbFaces = Integer.parseInt(fields.get(2).getValue()); |
| inVertexElt = false; |
| inFaceElt = true; |
| } |
| break; |
| case PROPERTY: |
| if (inVertexElt) { |
| ++vPropertiesNumber; |
| if (fields.size() != 3 || |
| (fields.get(1).getToken() != Token.CHAR && |
| fields.get(1).getToken() != Token.UCHAR && |
| fields.get(1).getToken() != Token.SHORT && |
| fields.get(1).getToken() != Token.USHORT && |
| fields.get(1).getToken() != Token.INT && |
| fields.get(1).getToken() != Token.UINT && |
| fields.get(1).getToken() != Token.FLOAT && |
| fields.get(1).getToken() != Token.DOUBLE)) { |
| complain(); |
| } |
| if (fields.get(2).getToken() == Token.X) { |
| xIndex = vPropertiesNumber; |
| }else if (fields.get(2).getToken() == Token.Y) { |
| yIndex = vPropertiesNumber; |
| }else if (fields.get(2).getToken() == Token.Z) { |
| zIndex = vPropertiesNumber; |
| } |
| } else if (inFaceElt) { |
| if (fields.size() != 5 || |
| fields.get(1).getToken() != Token.LIST && |
| (fields.get(2).getToken() != Token.CHAR && |
| fields.get(2).getToken() != Token.UCHAR && |
| fields.get(2).getToken() != Token.SHORT && |
| fields.get(2).getToken() != Token.USHORT && |
| fields.get(2).getToken() != Token.INT && |
| fields.get(2).getToken() != Token.UINT) || |
| (fields.get(3).getToken() != Token.CHAR && |
| fields.get(3).getToken() != Token.UCHAR && |
| fields.get(3).getToken() != Token.SHORT && |
| fields.get(3).getToken() != Token.USHORT && |
| fields.get(3).getToken() != Token.INT && |
| fields.get(3).getToken() != Token.UINT) || |
| fields.get(4).getToken() != Token.VERTEX_INDICES) { |
| complain(); |
| } |
| } else { |
| complain(); |
| } |
| break; |
| case END_HEADER: |
| inVertexElt = false; |
| inFaceElt = false; |
| parsing = false; |
| break; |
| default: |
| throw new ParseException("unable to parse line: " + line, 0); |
| } |
| } |
| ++vPropertiesNumber; |
| |
| // parse vertices |
| vertices = new Vector3D[nbVertices]; |
| for (int i = 0; i < nbVertices; ++i) { |
| fields = parseNextLine(); |
| if (fields.size() != vPropertiesNumber || |
| fields.get(xIndex).getToken() != Token.UNKNOWN || |
| fields.get(yIndex).getToken() != Token.UNKNOWN || |
| fields.get(zIndex).getToken() != Token.UNKNOWN) { |
| complain(); |
| } |
| vertices[i] = Vector3D.of(Double.parseDouble(fields.get(xIndex).getValue()), |
| Double.parseDouble(fields.get(yIndex).getValue()), |
| Double.parseDouble(fields.get(zIndex).getValue())); |
| } |
| |
| // parse faces |
| faces = new int[nbFaces][]; |
| for (int i = 0; i < nbFaces; ++i) { |
| fields = parseNextLine(); |
| if (fields.isEmpty() || |
| fields.size() != (Integer.parseInt(fields.get(0).getValue()) + 1)) { |
| complain(); |
| } |
| faces[i] = new int[fields.size() - 1]; |
| for (int j = 0; j < faces[i].length; ++j) { |
| faces[i][j] = Integer.parseInt(fields.get(j + 1).getValue()); |
| } |
| } |
| |
| } catch (NumberFormatException nfe) { |
| complain(); |
| } |
| } |
| |
| /** Complain about a bad line. |
| * @exception ParseException always thrown |
| */ |
| private void complain() throws ParseException { |
| throw new ParseException("unable to parse line: " + line, 0); |
| } |
| |
| /** Parse next line. |
| * @return parsed fields |
| * @exception IOException if stream cannot be read |
| * @exception ParseException if the line does not contain the expected number of fields |
| */ |
| private List<Field> parseNextLine() |
| throws IOException, ParseException { |
| final List<Field> fields = new ArrayList<>(); |
| line = br.readLine(); |
| if (line == null) { |
| throw new EOFException(); |
| } |
| final StringTokenizer tokenizer = new StringTokenizer(line); |
| while (tokenizer.hasMoreTokens()) { |
| fields.add(new Field(tokenizer.nextToken())); |
| } |
| return fields; |
| } |
| |
| /** Get the parsed vertices. |
| * @return parsed vertices |
| */ |
| public List<Vector3D> getVertices() { |
| return Arrays.asList(vertices); |
| } |
| |
| /** Get the parsed faces. |
| * @return parsed faces |
| */ |
| public List<int[]> getFaces() { |
| return Arrays.asList(faces); |
| } |
| |
| /** Tokens from PLY files. */ |
| private static enum Token { |
| PLY, FORMAT, ASCII, BINARY_BIG_ENDIAN, BINARY_LITTLE_ENDIAN, |
| COMMENT, ELEMENT, VERTEX, FACE, PROPERTY, LIST, OBJ_INFO, |
| CHAR, UCHAR, SHORT, USHORT, INT, UINT, FLOAT, DOUBLE, |
| X, Y, Z, VERTEX_INDICES, END_HEADER, UNKNOWN; |
| } |
| |
| /** Parsed line fields. */ |
| private static class Field { |
| |
| /** Token. */ |
| private final Token token; |
| |
| /** Value. */ |
| private final String value; |
| |
| /** Simple constructor. |
| * @param value field value |
| */ |
| public Field(final String value) { |
| Token parsedToken = null; |
| try { |
| parsedToken = Token.valueOf(value.toUpperCase()); |
| } catch (IllegalArgumentException iae) { |
| parsedToken = Token.UNKNOWN; |
| } |
| this.token = parsedToken; |
| this.value = value; |
| } |
| |
| /** Get the recognized token. |
| * @return recognized token |
| */ |
| public Token getToken() { |
| return token; |
| } |
| |
| /** Get the field value. |
| * @return field value |
| */ |
| public String getValue() { |
| return value; |
| } |
| |
| } |
| |
| } |