1 /* 2 * Licensed to the Apache Software Foundation (ASF) under one or more 3 * contributor license agreements. See the NOTICE file distributed with 4 * this work for additional information regarding copyright ownership. 5 * The ASF licenses this file to You under the Apache License, Version 2.0 6 * (the "License"); you may not use this file except in compliance with 7 * the License. You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 package org.apache.commons.math3.geometry.euclidean.threed; 18 19 import java.io.BufferedReader; 20 import java.io.EOFException; 21 import java.io.IOException; 22 import java.io.InputStream; 23 import java.io.InputStreamReader; 24 import java.text.ParseException; 25 import java.util.ArrayList; 26 import java.util.Arrays; 27 import java.util.List; 28 import java.util.StringTokenizer; 29 30 import org.apache.commons.math3.util.Precision; 31 32 /** This class is a small and incomplete parser for PLY files. 33 * <p> 34 * This parser is only intended for test purposes, it does not 35 * parse the full header, it does not handle all properties, 36 * it has rudimentary error handling. 37 * </p> 38 * @since 3.5 39 */ 40 public class PLYParser { 41 42 /** Parsed vertices. */ 43 private Vector3D[] vertices; 44 45 /** Parsed faces. */ 46 private int[][] faces; 47 48 /** Reader for PLY data. */ 49 private BufferedReader br; 50 51 /** Last parsed line. */ 52 private String line; 53 54 /** Simple constructor. 55 * @param stream stream to parse (closing it remains caller responsibility) 56 * @exception IOException if stream cannot be read 57 * @exception ParseException if stream content cannot be parsed 58 */ PLYParser(final InputStream stream)59 public PLYParser(final InputStream stream) 60 throws IOException, ParseException { 61 62 try { 63 br = new BufferedReader(new InputStreamReader(stream, "UTF-8")); 64 65 // parse the header 66 List<Field> fields = parseNextLine(); 67 if (fields.size() != 1 || fields.get(0).getToken() != Token.PLY) { 68 complain(); 69 } 70 71 boolean parsing = true; 72 int nbVertices = -1; 73 int nbFaces = -1; 74 int xIndex = -1; 75 int yIndex = -1; 76 int zIndex = -1; 77 int vPropertiesNumber = -1; 78 boolean inVertexElt = false; 79 boolean inFaceElt = false; 80 while (parsing) { 81 fields = parseNextLine(); 82 if (fields.size() < 1) { 83 complain(); 84 } 85 switch (fields.get(0).getToken()) { 86 case FORMAT: 87 if (fields.size() != 3 || 88 fields.get(1).getToken() != Token.ASCII || 89 fields.get(2).getToken() != Token.UNKNOWN || 90 !Precision.equals(Double.parseDouble(fields.get(2).getValue()), 1.0, 0.001)) { 91 complain(); 92 } 93 inVertexElt = false; 94 inFaceElt = false; 95 break; 96 case COMMENT: 97 // we just ignore this line 98 break; 99 case ELEMENT: 100 if (fields.size() != 3 || 101 (fields.get(1).getToken() != Token.VERTEX && fields.get(1).getToken() != Token.FACE) || 102 fields.get(2).getToken() != Token.UNKNOWN) { 103 complain(); 104 } 105 if (fields.get(1).getToken() == Token.VERTEX) { 106 nbVertices = Integer.parseInt(fields.get(2).getValue()); 107 inVertexElt = true; 108 inFaceElt = false; 109 } else { 110 nbFaces = Integer.parseInt(fields.get(2).getValue()); 111 inVertexElt = false; 112 inFaceElt = true; 113 } 114 break; 115 case PROPERTY: 116 if (inVertexElt) { 117 ++vPropertiesNumber; 118 if (fields.size() != 3 || 119 (fields.get(1).getToken() != Token.CHAR && 120 fields.get(1).getToken() != Token.UCHAR && 121 fields.get(1).getToken() != Token.SHORT && 122 fields.get(1).getToken() != Token.USHORT && 123 fields.get(1).getToken() != Token.INT && 124 fields.get(1).getToken() != Token.UINT && 125 fields.get(1).getToken() != Token.FLOAT && 126 fields.get(1).getToken() != Token.DOUBLE)) { 127 complain(); 128 } 129 if (fields.get(2).getToken() == Token.X) { 130 xIndex = vPropertiesNumber; 131 }else if (fields.get(2).getToken() == Token.Y) { 132 yIndex = vPropertiesNumber; 133 }else if (fields.get(2).getToken() == Token.Z) { 134 zIndex = vPropertiesNumber; 135 } 136 } else if (inFaceElt) { 137 if (fields.size() != 5 || 138 fields.get(1).getToken() != Token.LIST && 139 (fields.get(2).getToken() != Token.CHAR && 140 fields.get(2).getToken() != Token.UCHAR && 141 fields.get(2).getToken() != Token.SHORT && 142 fields.get(2).getToken() != Token.USHORT && 143 fields.get(2).getToken() != Token.INT && 144 fields.get(2).getToken() != Token.UINT) || 145 (fields.get(3).getToken() != Token.CHAR && 146 fields.get(3).getToken() != Token.UCHAR && 147 fields.get(3).getToken() != Token.SHORT && 148 fields.get(3).getToken() != Token.USHORT && 149 fields.get(3).getToken() != Token.INT && 150 fields.get(3).getToken() != Token.UINT) || 151 fields.get(4).getToken() != Token.VERTEX_INDICES) { 152 complain(); 153 } 154 } else { 155 complain(); 156 } 157 break; 158 case END_HEADER: 159 inVertexElt = false; 160 inFaceElt = false; 161 parsing = false; 162 break; 163 default: 164 throw new ParseException("unable to parse line: " + line, 0); 165 } 166 } 167 ++vPropertiesNumber; 168 169 // parse vertices 170 vertices = new Vector3D[nbVertices]; 171 for (int i = 0; i < nbVertices; ++i) { 172 fields = parseNextLine(); 173 if (fields.size() != vPropertiesNumber || 174 fields.get(xIndex).getToken() != Token.UNKNOWN || 175 fields.get(yIndex).getToken() != Token.UNKNOWN || 176 fields.get(zIndex).getToken() != Token.UNKNOWN) { 177 complain(); 178 } 179 vertices[i] = new Vector3D(Double.parseDouble(fields.get(xIndex).getValue()), 180 Double.parseDouble(fields.get(yIndex).getValue()), 181 Double.parseDouble(fields.get(zIndex).getValue())); 182 } 183 184 // parse faces 185 faces = new int[nbFaces][]; 186 for (int i = 0; i < nbFaces; ++i) { 187 fields = parseNextLine(); 188 if (fields.isEmpty() || 189 fields.size() != (Integer.parseInt(fields.get(0).getValue()) + 1)) { 190 complain(); 191 } 192 faces[i] = new int[fields.size() - 1]; 193 for (int j = 0; j < faces[i].length; ++j) { 194 faces[i][j] = Integer.parseInt(fields.get(j + 1).getValue()); 195 } 196 } 197 198 } catch (NumberFormatException nfe) { 199 complain(); 200 } 201 } 202 203 /** Complain about a bad line. 204 * @exception ParseException always thrown 205 */ complain()206 private void complain() throws ParseException { 207 throw new ParseException("unable to parse line: " + line, 0); 208 } 209 210 /** Parse next line. 211 * @return parsed fields 212 * @exception IOException if stream cannot be read 213 * @exception ParseException if the line does not contain the expected number of fields 214 */ parseNextLine()215 private List<Field> parseNextLine() 216 throws IOException, ParseException { 217 final List<Field> fields = new ArrayList<Field>(); 218 line = br.readLine(); 219 if (line == null) { 220 throw new EOFException(); 221 } 222 final StringTokenizer tokenizer = new StringTokenizer(line); 223 while (tokenizer.hasMoreTokens()) { 224 fields.add(new Field(tokenizer.nextToken())); 225 } 226 return fields; 227 } 228 229 /** Get the parsed vertices. 230 * @return parsed vertices 231 */ getVertices()232 public List<Vector3D> getVertices() { 233 return Arrays.asList(vertices); 234 } 235 236 /** Get the parsed faces. 237 * @return parsed faces 238 */ getFaces()239 public List<int[]> getFaces() { 240 return Arrays.asList(faces); 241 } 242 243 /** Tokens from PLY files. */ 244 private static enum Token { 245 PLY, FORMAT, ASCII, BINARY_BIG_ENDIAN, BINARY_LITTLE_ENDIAN, 246 COMMENT, ELEMENT, VERTEX, FACE, PROPERTY, LIST, OBJ_INFO, 247 CHAR, UCHAR, SHORT, USHORT, INT, UINT, FLOAT, DOUBLE, 248 X, Y, Z, VERTEX_INDICES, END_HEADER, UNKNOWN; 249 } 250 251 /** Parsed line fields. */ 252 private static class Field { 253 254 /** Token. */ 255 private final Token token; 256 257 /** Value. */ 258 private final String value; 259 260 /** Simple constructor. 261 * @param value field value 262 */ Field(final String value)263 public Field(final String value) { 264 Token parsedToken = null; 265 try { 266 parsedToken = Token.valueOf(value.toUpperCase()); 267 } catch (IllegalArgumentException iae) { 268 parsedToken = Token.UNKNOWN; 269 } 270 this.token = parsedToken; 271 this.value = value; 272 } 273 274 /** Get the recognized token. 275 * @return recognized token 276 */ getToken()277 public Token getToken() { 278 return token; 279 } 280 281 /** Get the field value. 282 * @return field value 283 */ getValue()284 public String getValue() { 285 return value; 286 } 287 288 } 289 290 } 291