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