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