View Javadoc
1   /*
2    * Copyright (C) 2012 Alberto Irurueta Carro (alberto@irurueta.com)
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *         http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package com.irurueta.geometry.io;
17  
18  import java.io.File;
19  import java.io.IOException;
20  
21  public class LoaderSTL extends Loader {
22      /**
23       * Constant defining the default value of maximum number of vertices to keep
24       * in a chunk. This is 65535, which corresponds to the maximum value allowed
25       * by graphical layer such as OpenGL when working with Vertex Buffer Objects.
26       */
27      public static final int DEFAULT_MAX_VERTICES_IN_CHUNK = 0xffff;
28  
29      /**
30       * Minimum allowed value for maximum number of vertices in chunk, which is
31       * one.
32       */
33      public static final int MIN_MAX_VERTICES_IN_CHUNK = 1;
34  
35      /**
36       * Amount of progress variation (1%) used to notify progress.
37       */
38      public static final float PROGRESS_DELTA = 0.01f;
39  
40      private LoaderIteratorSTL loaderIterator;
41  
42      private int maxVerticesInChunk;
43  
44      /**
45       * Constructor.
46       */
47      public LoaderSTL() {
48          loaderIterator = null;
49          maxVerticesInChunk = DEFAULT_MAX_VERTICES_IN_CHUNK;
50      }
51  
52      /**
53       * Constructor.
54       *
55       * @param maxVerticesInChunk Maximum number of vertices allowed in a chunk.
56       *                           Once this value is exceeded when loading a file, a new chunk of data is
57       *                           created.
58       * @throws IllegalArgumentException if maximum number of vertices allowed in
59       *                                  a chunk is lower than 1.
60       */
61      public LoaderSTL(final int maxVerticesInChunk) {
62          loaderIterator = null;
63          internalSetMaxVerticesInChunk(maxVerticesInChunk);
64      }
65  
66      /**
67       * Constructor.
68       *
69       * @param f file to be loaded.
70       * @throws IOException if an I/O error occurs.
71       */
72      public LoaderSTL(final File f) throws IOException {
73          super(f);
74          loaderIterator = null;
75          maxVerticesInChunk = DEFAULT_MAX_VERTICES_IN_CHUNK;
76      }
77  
78      /**
79       * Constructor.
80       *
81       * @param f                  file to be loaded.
82       * @param maxVerticesInChunk Maximum number of vertices allowed in a chunk.
83       *                           Once this value is exceeded when loading a file, a new chunk of data is
84       *                           created.
85       * @throws IllegalArgumentException if maximum number of vertices allowed in
86       *                                  a chunk is lower than 1.
87       * @throws IOException              if an I/O error occurs.
88       */
89      public LoaderSTL(final File f, final int maxVerticesInChunk) throws IOException {
90          super(f);
91          loaderIterator = null;
92          internalSetMaxVerticesInChunk(maxVerticesInChunk);
93      }
94  
95      /**
96       * Constructor.
97       *
98       * @param listener listener to be notified of loading progress and when
99       *                 loading process starts or finishes.
100      */
101     public LoaderSTL(final LoaderListener listener) {
102         super(listener);
103         maxVerticesInChunk = DEFAULT_MAX_VERTICES_IN_CHUNK;
104     }
105 
106     /**
107      * Constructor.
108      *
109      * @param listener           listener to be notified of loading progress and when
110      *                           loading process starts or finishes.
111      * @param maxVerticesInChunk Maximum number of vertices allowed in a chunk.
112      *                           Once this value is exceeded when loading a file, a new chunk of data is
113      *                           created.
114      * @throws IllegalArgumentException if maximum number of vertices allowed in
115      *                                  a chunk is lower than 1.
116      */
117     public LoaderSTL(final LoaderListener listener, final int maxVerticesInChunk) {
118         super(listener);
119         loaderIterator = null;
120         internalSetMaxVerticesInChunk(maxVerticesInChunk);
121     }
122 
123     /**
124      * Constructor.
125      *
126      * @param f        file to be loaded.
127      * @param listener listener to be notified of loading progress and when
128      *                 loading process starts or finishes.
129      * @throws IOException if an I/O error occurs.
130      */
131     public LoaderSTL(final File f, final LoaderListener listener) throws IOException {
132         super(f, listener);
133         loaderIterator = null;
134         maxVerticesInChunk = DEFAULT_MAX_VERTICES_IN_CHUNK;
135     }
136 
137     /**
138      * Constructor.
139      *
140      * @param f                  file to be loaded.
141      * @param listener           listener to be notified of loading progress and when
142      *                           loading process starts or finishes.
143      * @param maxVerticesInChunk Maximum number of vertices allowed in a chunk.
144      *                           Once this value is exceeded when loading a file, a new chunk of data is
145      *                           created.
146      * @throws IllegalArgumentException if maximum number of vertices allowed in
147      *                                  a chunk is lower than 1.
148      * @throws IOException              if an I/O error occurs.
149      */
150     public LoaderSTL(final File f, final LoaderListener listener, final int maxVerticesInChunk) throws IOException {
151         super(f, listener);
152         loaderIterator = null;
153         internalSetMaxVerticesInChunk(maxVerticesInChunk);
154     }
155 
156     /**
157      * Sets maximum number of vertices allowed in a chunk.
158      * Once this value is exceeded when loading a file, a new chunk of data is
159      * created.
160      *
161      * @param maxVerticesInChunk maximum allowed number of vertices to be set.
162      * @throws IllegalArgumentException if provided value is lower than 1.
163      * @throws LockedException          if this loader is currently loading a file.
164      */
165     public void setMaxVerticesInChunk(final int maxVerticesInChunk) throws LockedException {
166         if (isLocked()) {
167             throw new LockedException();
168         }
169         internalSetMaxVerticesInChunk(maxVerticesInChunk);
170     }
171 
172     /**
173      * Returns maximum number of vertices allowed in a chunk.
174      * Once this value is exceeded when loading a file, a new chunk of data is
175      * created.
176      *
177      * @return maximum number of vertices allowed in a chunk.
178      */
179     public int getMaxVerticesInChunk() {
180         return maxVerticesInChunk;
181     }
182 
183     /**
184      * If loader is ready to start loading a file.
185      * This is true once a file has been provided.
186      *
187      * @return true if ready to start loading a file, false otherwise.
188      */
189     @Override
190     public boolean isReady() {
191         return hasFile();
192     }
193 
194     /**
195      * Returns mesh format supported by this class, which is MESH_FORMAT_STL.
196      *
197      * @return mesh format supported by this class.
198      */
199     @Override
200     public MeshFormat getMeshFormat() {
201         return MeshFormat.MESH_FORMAT_STL;
202     }
203 
204     /**
205      * Determines if provided file is a valid file that can be read by this
206      * loader.
207      *
208      * @return true if file is valid, false otherwise.
209      * @throws LockedException raised if this instance is already locked.
210      * @throws IOException     if an I/O error occurs.
211      */
212     @Override
213     public boolean isValidFile() throws LockedException, IOException {
214         if (!hasFile()) {
215             throw new IOException();
216         }
217         if (isLocked()) {
218             throw new LockedException();
219         }
220         return true;
221     }
222 
223     /**
224      * Starts the loading process of provided file.
225      * This method returns a LoaderIterator to start the iterative process to
226      * load a file in small chunks of data.
227      *
228      * @return a loader iterator to read the file in a step-by-step process.
229      * @throws LockedException   raised if this instance is already locked.
230      * @throws NotReadyException raised if this instance is not yet ready.
231      * @throws IOException       if an I/O error occurs.
232      * @throws LoaderException   if file is corrupted or cannot be interpreted.
233      */
234     @Override
235     public LoaderIterator load() throws LockedException, NotReadyException, IOException, LoaderException {
236         if (isLocked()) {
237             throw new LockedException();
238         }
239         if (!isReady()) {
240             throw new NotReadyException();
241         }
242 
243         setLocked(true);
244         if (listener != null) {
245             listener.onLoadStart(this);
246         }
247 
248         loaderIterator = new LoaderIteratorSTL(this);
249         loaderIterator.setListener(new LoaderIteratorListenerImpl(this));
250         return loaderIterator;
251     }
252 
253     /**
254      * Returns name for the 3D object.
255      *
256      * @return name for the 3D object.
257      */
258     protected String getSolidName() {
259         return loaderIterator != null ? loaderIterator.getSolidName() : null;
260     }
261 
262     /**
263      * Gets number of vertices contained in the file.
264      *
265      * @return number of vertices contained in the file.
266      */
267     protected Long getNumberOfVertices() {
268         return loaderIterator != null ? loaderIterator.getNumberOfVertices() : null;
269     }
270 
271 
272     /**
273      * Internal method to set maximum number of vertices allowed in a chunk.
274      * This method is reused both in the constructor and in the setter of
275      * maximum number of vertices allowed in a chunk.
276      *
277      * @param maxVerticesInChunk maximum allowed number of vertices to be set.
278      * @throws IllegalArgumentException if provided value is lower than 1.
279      */
280     private void internalSetMaxVerticesInChunk(final int maxVerticesInChunk) {
281         if (maxVerticesInChunk < MIN_MAX_VERTICES_IN_CHUNK) {
282             throw new IllegalArgumentException();
283         }
284 
285         this.maxVerticesInChunk = maxVerticesInChunk;
286     }
287 
288     /**
289      * Internal listener to be notified when loading process finishes.
290      * This listener is used to free resources when loading process finishes.
291      */
292     private class LoaderIteratorListenerImpl implements LoaderIteratorListener {
293         /**
294          * Reference to Loader loading an STL file.
295          */
296         private final LoaderSTL loader;
297 
298         public LoaderIteratorListenerImpl(final LoaderSTL loader) {
299             this.loader = loader;
300         }
301 
302         /**
303          * Method to be notified when the loading process finishes.
304          *
305          * @param iterator iterator loading the file in chunks.
306          */
307         @Override
308         public void onIteratorFinished(final LoaderIterator iterator) {
309             // because iterator is finished, we should allow subsequent calls to
310             // load method
311             try {
312                 // attempt restart stream to initial position
313                 reader.seek(0);
314             } catch (final Exception ignore) {
315                 // this is the best effort operation, if it fails it is ignored
316             }
317 
318             // on subsequent calls
319             if (listener != null) {
320                 listener.onLoadEnd(loader);
321             }
322             setLocked(false);
323         }
324     }
325 
326     /**
327      * Loader iterator in charge of loading file data in small chunks.
328      * Usually data is divided in chunks small enough that can be directly
329      * loaded by graphical layers such as OpenGL (which has a limit of 65535
330      * indices when using Vertex Buffer Objects, which increase graphical
331      * performance).
332      */
333     private class LoaderIteratorSTL implements LoaderIterator {
334 
335         /**
336          * Reference to loader loading STL file.
337          */
338         private final LoaderSTL loader;
339 
340         /**
341          * X coordinate of the latest point that has been read.
342          */
343         private float coordX;
344 
345         /**
346          * Y coordinate of the latest point that has been read.
347          */
348         private float coordY;
349 
350         /**
351          * Z coordinate of the latest point that has been read.
352          */
353         private float coordZ;
354 
355         /**
356          * X coordinate of the latest point normal that has been read.
357          */
358         private float nX;
359 
360         /**
361          * Y coordinate of the latest point normal that has been read.
362          */
363         private float nY;
364 
365         /**
366          * Z coordinate of the latest point normal that has been read.
367          */
368         private float nZ;
369 
370         // coordinates for bounding box in a chunk
371 
372         /**
373          * X coordinate of the minimum point forming the bounding box in a chunk
374          * of data. This value will be updated while the chunk is being filled.
375          */
376         private float minX;
377 
378         /**
379          * Y coordinate of the minimum point forming the bounding box in a chunk
380          * of data. This value will be updated while the chunk is being filled.
381          */
382         private float minY;
383 
384         /**
385          * Z coordinate of the minimum point forming the bounding box in a chunk
386          * of data. This value will be updated while the chunk is being filled.
387          */
388         private float minZ;
389 
390         /**
391          * X coordinate of the maximum point forming the bounding box in a chunk
392          * of data. This value will be updated while the chunk is being filled.
393          */
394         private float maxX;
395 
396         /**
397          * Y coordinate of the maximum point forming the bounding box in a chunk
398          * of data. This value will be updated while the chunk is being filled.
399          */
400         private float maxY;
401 
402         /**
403          * Z coordinate of the maximum point forming the bounding box in a chunk
404          * of data. This value will be updated while the chunk is being filled.
405          */
406         private float maxZ;
407 
408         /**
409          * Reference to the listener of this loader iterator. This listener will
410          * be notified when the loading process finishes so that resources can
411          * be freed.
412          */
413         private LoaderIteratorListener listener;
414 
415         /**
416          * Array containing vertices coordinates to be added to current chunk
417          * of data.
418          */
419         private float[] coordsInChunkArray;
420 
421         /**
422          * Array containing normal coordinates to be added to current chunk of
423          * data.
424          */
425         private float[] normalsInChunkArray;
426 
427         /**
428          * Array containing indices to be added to current chunk of data. Notice
429          * that these indices are not the original indices appearing in the file.
430          * Instead, they are indices referring to data in current chunk,
431          * accounting for duplicate points, etc. This way, indices in a chunk
432          * can be directly used to draw the chunk of data by the graphical layer.
433          */
434         private int[] indicesInChunkArray;
435 
436         /**
437          * Number of vertices stored in chunk.
438          */
439         private int verticesInChunk;
440 
441         /**
442          * Number of indices stored in chunk.
443          */
444         private int indicesInChunk;
445 
446         /**
447          * Size of indices stored in chunk.
448          */
449         private int indicesInChunkSize;
450 
451         /**
452          * Indicates if file is in ASCII format or in binary format.
453          */
454         private boolean isAscii;
455 
456         /**
457          * Indicates number of triangles that are contained in the file (when
458          * available).
459          */
460         private long numberOfTriangles;
461 
462         /**
463          * Indicates current triangle being read (when available).
464          */
465         private long currentTriangle;
466 
467         /**
468          * Contains number of vertices contained in the file.
469          */
470         private long numberOfVertices;
471 
472         /**
473          * Indicates when end of file has been reached.
474          */
475         private boolean endOfFileReached;
476 
477         /**
478          * Contains name for the 3D object.
479          */
480         private String solidName;
481 
482         /**
483          * Constant defining beginning of 3D file.
484          */
485         public static final String ASCII_START = "solid";
486 
487         /**
488          * Constant defining end of 3D file.
489          */
490         public static final String ASCII_END = "endsolid";
491 
492         /**
493          * Constant defining a face (i.e. triangle or polygon).
494          */
495         public static final String ASCII_FACET = "facet";
496 
497         /**
498          * Constant defining a normal.
499          */
500         public static final String ASCII_NORMAL = "normal";
501 
502         /**
503          * Constant defining grouping levels.
504          */
505         public static final String ASCII_OUTER = "outer";
506 
507         /**
508          * Constant defining a loop that will contain vertices.
509          */
510         public static final String ASCII_LOOP = "loop";
511 
512         /**
513          * Constant defining a vertex.
514          */
515         public static final String ASCII_VERTEX = "vertex";
516 
517         /**
518          * Constant defining end of loop containing vertices.
519          */
520         public static final String ASCII_END_LOOP = "endloop";
521 
522         /**
523          * Constant defining end of 3D face (i.e. triangle or polygon).
524          */
525         public static final String ASCII_END_FACET = "endfacet";
526 
527         /**
528          * Header size for binary format.
529          */
530         public static final int BINARY_HEADER_SIZE = 80;
531 
532         /**
533          * Constant defining number of vertices in a triangle.
534          */
535         public static final int VERTICES_PER_TRIANGLE = 3;
536 
537         /**
538          * Constructor.
539          *
540          * @param loader reference to loader loading binary file.
541          * @throws IOException     if an I/O error occurs.
542          * @throws LoaderException if file data is corrupt or cannot be
543          *                         understood.
544          */
545         public LoaderIteratorSTL(final LoaderSTL loader) throws IOException, LoaderException {
546             this.loader = loader;
547             nX = nY = nZ = 1.0f;
548             listener = null;
549             coordsInChunkArray = null;
550             normalsInChunkArray = null;
551             indicesInChunkArray = null;
552 
553             verticesInChunk = indicesInChunk = indicesInChunkSize = 0;
554 
555             minX = minY = minZ = Float.MAX_VALUE;
556             maxX = maxY = maxZ = -Float.MAX_VALUE;
557 
558             isAscii = false;
559             numberOfTriangles = currentTriangle = 0;
560             endOfFileReached = false;
561 
562             numberOfVertices = 0;
563 
564             setUp();
565         }
566 
567         /**
568          * Method to set listener of this loader iterator.
569          * This listener will be notified when the loading process finishes.
570          *
571          * @param listener listener of this loader iterator.
572          */
573         public void setListener(final LoaderIteratorListener listener) {
574             this.listener = listener;
575         }
576 
577         /**
578          * Indicates if there is another chunk of data to be loaded.
579          *
580          * @return true if there is another chunk of data, false otherwise.
581          */
582         @Override
583         public boolean hasNext() {
584             if (isAscii) {
585                 return !endOfFileReached;
586             } else {
587                 return currentTriangle < numberOfTriangles;
588             }
589         }
590 
591         /**
592          * Loads and returns next chunk of data, if available.
593          *
594          * @return next chunk of data.
595          * @throws NotAvailableException thrown if no more data is available.
596          * @throws LoaderException       if file data is corrupt or cannot be
597          *                               understood.
598          * @throws IOException           if an I/O error occurs.
599          */
600         @Override
601         public DataChunk next() throws NotAvailableException, LoaderException, IOException {
602 
603             if (reader == null) {
604                 throw new IOException();
605             }
606 
607             if (!hasNext()) {
608                 throw new NotAvailableException();
609             }
610 
611             initChunkArrays();
612 
613             // reset chunk bounding box values
614             minX = minY = minZ = Float.MAX_VALUE;
615             maxX = maxY = maxZ = -Float.MAX_VALUE;
616 
617             // read data until chunk is full
618             var endOfChunk = false;
619             final var fileLength = file.length();
620 
621             final var progressStep = Math.max((long) (LoaderSTL.PROGRESS_DELTA * fileLength), 1);
622             var previousPos = 0L;
623 
624             try {
625                 if (isAscii) {
626 
627                     // ascii format
628                     String word;
629                     do {
630                         // read facet
631                         word = readNonEmptyWord();
632                         if (word == null) {
633                             // undefined word
634                             throw new LoaderException();
635                         }
636 
637                         if (word.equalsIgnoreCase(ASCII_FACET)) {
638                             // read normal
639                             word = readNonEmptyWord();
640                             if (word == null) {
641                                 // undefined word
642                                 throw new LoaderException();
643                             }
644 
645                             if (word.equalsIgnoreCase(ASCII_NORMAL)) {
646                                 // read 3 normal values
647                                 word = readNonEmptyWord();
648                                 nX = Float.parseFloat(word);
649                                 word = readNonEmptyWord();
650                                 nY = Float.parseFloat(word);
651                                 word = readNonEmptyWord();
652                                 nZ = Float.parseFloat(word);
653                             } else {
654                                 // unexpected word
655                                 throw new LoaderException();
656                             }
657 
658                         } else if (word.equalsIgnoreCase(ASCII_OUTER)) {
659                             // next word has to be "loop"
660                             word = readNonEmptyWord();
661                             if (word == null) {
662                                 // undefined word
663                                 throw new LoaderException();
664                             }
665 
666                             if (!word.equalsIgnoreCase(ASCII_LOOP)) {
667                                 // unexpected word
668                                 throw new LoaderException();
669                             }
670 
671                         } else if (word.equalsIgnoreCase(ASCII_VERTEX)) {
672                             // read vertex data
673                             word = readNonEmptyWord();
674                             coordX = Float.parseFloat(word);
675                             word = readNonEmptyWord();
676                             coordY = Float.parseFloat(word);
677                             word = readNonEmptyWord();
678                             coordZ = Float.parseFloat(word);
679 
680                             // add coordinates into chunk arrays
681                             addNewVertexDataToChunk();
682 
683                             // check if chunk is full
684                             if (verticesInChunk == loader.maxVerticesInChunk) {
685                                 // no more vertices can be added to this chunk
686                                 break;
687                             }
688 
689                         } else if (word.equalsIgnoreCase(ASCII_END_LOOP)) {
690                             // check if chunk is full
691                             if (verticesInChunk + VERTICES_PER_TRIANGLE >= loader.maxVerticesInChunk) {
692                                 // no more triangles vertices can be added to
693                                 // this chunk
694                                 break;
695                             }
696 
697                         } else if (word.equalsIgnoreCase(ASCII_END_FACET)) {
698                             // check if chunk is full
699                             if (verticesInChunk + VERTICES_PER_TRIANGLE >= loader.maxVerticesInChunk) {
700                                 // no more triangles vertices can be added to
701                                 // this chunk
702                                 break;
703                             }
704                         } else if (word.equalsIgnoreCase(ASCII_END)) {
705                             endOfFileReached = true;
706                             break;
707                         } else {
708                             // unexpected word
709                             throw new LoaderException();
710                         }
711 
712                         // compute progress
713                         if ((loader.listener != null) && (reader.getPosition() - previousPos) >= progressStep) {
714                             previousPos = reader.getPosition();
715                             loader.listener.onLoadProgressChange(loader,
716                                     (float) (reader.getPosition()) / fileLength);
717                         }
718 
719                     } while (!endOfFileReached);
720 
721                 } else {
722                     do {
723                         // read vertex data (triangle normal, triangle vertices
724                         // and two bytes attribute byte count
725 
726                         // read vertex normals
727                         nX = reader.readFloat(EndianType.LITTLE_ENDIAN_TYPE);
728                         nY = reader.readFloat(EndianType.LITTLE_ENDIAN_TYPE);
729                         nZ = reader.readFloat(EndianType.LITTLE_ENDIAN_TYPE);
730 
731                         // read 1st vertex coordinates of triangle
732                         coordX = reader.readFloat(EndianType.LITTLE_ENDIAN_TYPE);
733                         coordY = reader.readFloat(EndianType.LITTLE_ENDIAN_TYPE);
734                         coordZ = reader.readFloat(EndianType.LITTLE_ENDIAN_TYPE);
735 
736                         // add coordinates into chunk arrays
737                         addNewVertexDataToChunk();
738 
739                         // read 2nd vertex coordinates of triangle
740                         coordX = reader.readFloat(EndianType.LITTLE_ENDIAN_TYPE);
741                         coordY = reader.readFloat(EndianType.LITTLE_ENDIAN_TYPE);
742                         coordZ = reader.readFloat(EndianType.LITTLE_ENDIAN_TYPE);
743 
744                         // add coordinates into chunk arrays
745                         addNewVertexDataToChunk();
746 
747                         // read 3rd vertex coordinates of triangle
748                         coordX = reader.readFloat(EndianType.LITTLE_ENDIAN_TYPE);
749                         coordY = reader.readFloat(EndianType.LITTLE_ENDIAN_TYPE);
750                         coordZ = reader.readFloat(EndianType.LITTLE_ENDIAN_TYPE);
751 
752                         // add coordinates into chunk arrays
753                         addNewVertexDataToChunk();
754 
755                         // read two bytes attribute byte count
756                         reader.readUnsignedShort(EndianType.LITTLE_ENDIAN_TYPE);
757 
758                         currentTriangle++;
759 
760                         // compute progress
761                         if ((loader.listener != null) && (reader.getPosition() - previousPos) >= progressStep) {
762                             previousPos = reader.getPosition();
763                             loader.listener.onLoadProgressChange(loader,
764                                     (float) (reader.getPosition()) / fileLength);
765                         }
766 
767                         if (verticesInChunk + VERTICES_PER_TRIANGLE >= loader.maxVerticesInChunk) {
768                             // no more triangles vertices can be added to this
769                             // chunk
770                             endOfChunk = true;
771                             break;
772                         }
773 
774                     } while (!reader.isEndOfStream() && currentTriangle < numberOfTriangles);
775 
776                     // file ended before all triangles were read
777                     if (currentTriangle < numberOfTriangles && !endOfChunk) {
778                         throw new LoaderException();
779                     }
780                 }
781             } catch (final IOException | LoaderException e) {
782                 throw e;
783             } catch (final Exception e) {
784                 throw new LoaderException(e);
785             }
786 
787             // trim arrays to store only needed data
788             trimArrays();
789 
790             // Instantiate DataChunk with chunk arrays
791             final var dataChunk = new DataChunk();
792 
793             dataChunk.setVerticesCoordinatesData(coordsInChunkArray);
794             dataChunk.setMinX(minX);
795             dataChunk.setMinY(minY);
796             dataChunk.setMinZ(minZ);
797             dataChunk.setMaxX(maxX);
798             dataChunk.setMaxY(maxY);
799             dataChunk.setMaxZ(maxZ);
800 
801             dataChunk.setIndicesData(indicesInChunkArray);
802 
803             dataChunk.setNormalsData(normalsInChunkArray);
804 
805             if (!hasNext() && listener != null) {
806                 // notify iterator finished
807                 listener.onIteratorFinished(this);
808             }
809 
810             // if no more chunks are available, then close input reader
811             if (!hasNext()) {
812                 reader.close();
813             }
814 
815             return dataChunk;
816         }
817 
818         /**
819          * Returns name for the 3D object.
820          *
821          * @return name for the 3D object.
822          */
823         public String getSolidName() {
824             return solidName;
825         }
826 
827         /**
828          * Gets number of vertices contained in the file.
829          *
830          * @return number of vertices contained in the file.
831          */
832         public long getNumberOfVertices() {
833             return numberOfVertices;
834         }
835 
836         /**
837          * Internal method to read a word from ASCII file.
838          *
839          * @return next word that has been read.
840          * @throws IOException if an I/O error occurs.
841          */
842         private String readNonEmptyWord() throws IOException {
843             // read normal
844             String word;
845             do {
846                 // ignore empty words
847                 // (line feeds, etc.)
848                 word = reader.readWord();
849             } while (word != null && word.isEmpty());
850             return word;
851         }
852 
853         /**
854          * Initializes arrays forming current chunk of data.
855          */
856         private void initChunkArrays() {
857             coordsInChunkArray = new float[loader.maxVerticesInChunk * VERTICES_PER_TRIANGLE];
858             normalsInChunkArray = new float[loader.maxVerticesInChunk * VERTICES_PER_TRIANGLE];
859             indicesInChunkArray = new int[loader.maxVerticesInChunk];
860 
861             verticesInChunk = indicesInChunk = 0;
862             indicesInChunkSize = loader.maxVerticesInChunk;
863         }
864 
865         /**
866          * Adds data of last vertex being loaded to current chunk of data as a
867          * new vertex.
868          */
869         private void addNewVertexDataToChunk() {
870             var pos = 3 * verticesInChunk;
871 
872             coordsInChunkArray[pos] = coordX;
873             normalsInChunkArray[pos] = nX;
874 
875             pos++;
876 
877             coordsInChunkArray[pos] = coordY;
878             normalsInChunkArray[pos] = nY;
879 
880             pos++;
881 
882             coordsInChunkArray[pos] = coordZ;
883             normalsInChunkArray[pos] = nZ;
884 
885             // update bounding box values
886             if (coordX < minX) {
887                 minX = coordX;
888             }
889             if (coordY < minY) {
890                 minY = coordY;
891             }
892             if (coordZ < minZ) {
893                 minZ = coordZ;
894             }
895 
896             if (coordX > maxX) {
897                 maxX = coordX;
898             }
899             if (coordY > maxY) {
900                 maxY = coordY;
901             }
902             if (coordZ > maxZ) {
903                 maxZ = coordZ;
904             }
905 
906             // if arrays of indices become full, we need to resize them
907             if (indicesInChunk >= indicesInChunkSize) {
908                 increaseIndicesArraySize();
909             }
910             indicesInChunkArray[indicesInChunk] = verticesInChunk;
911 
912             verticesInChunk++;
913             indicesInChunk++;
914         }
915 
916         /**
917          * Increases size of arrays of data. This method is called when needed.
918          */
919         private void increaseIndicesArraySize() {
920             final var newIndicesInChunkSize = indicesInChunkSize + loader.maxVerticesInChunk;
921             final var newIndicesInChunkArray = new int[newIndicesInChunkSize];
922 
923             // copy contents of old array
924             System.arraycopy(indicesInChunkArray, 0, newIndicesInChunkArray, 0, indicesInChunkSize);
925 
926             // set new arrays and new size
927             indicesInChunkArray = newIndicesInChunkArray;
928             indicesInChunkSize = newIndicesInChunkSize;
929         }
930 
931         /**
932          * Trims arrays of data to reduce size of arrays to fit chunk data. This
933          * method is loaded just before copying data to chunk being returned.
934          */
935         private void trimArrays() {
936             if (verticesInChunk > 0) {
937                 final var elems = verticesInChunk * VERTICES_PER_TRIANGLE;
938 
939                 final var newCoordsInChunkArray = new float[elems];
940                 final var newNormalsInChunkArray = new float[elems];
941 
942                 // copy contents of old arrays
943                 System.arraycopy(coordsInChunkArray, 0, newCoordsInChunkArray, 0, elems);
944                 System.arraycopy(normalsInChunkArray, 0, newNormalsInChunkArray, 0, elems);
945 
946                 // set new arrays
947                 coordsInChunkArray = newCoordsInChunkArray;
948                 normalsInChunkArray = newNormalsInChunkArray;
949             } else {
950                 // allow garbage collection
951                 coordsInChunkArray = null;
952                 normalsInChunkArray = null;
953             }
954 
955             if (indicesInChunk > 0) {
956                 final var newIndicesInChunkArray = new int[indicesInChunk];
957                 System.arraycopy(indicesInChunkArray, 0, newIndicesInChunkArray, 0, indicesInChunk);
958 
959                 // set new array
960                 indicesInChunkArray = newIndicesInChunkArray;
961             } else {
962                 // allow garbage collection
963                 indicesInChunkArray = null;
964             }
965         }
966 
967         /**
968          * Setups loader iterator. This method is called when constructing
969          * this iterator.
970          *
971          * @throws IOException     if an I/O error occurs.
972          * @throws LoaderException if data is corrupted or cannot be understood.
973          */
974         private void setUp() throws IOException, LoaderException {
975             // read start of file to determine whether it is in ascii or binary
976             // format
977 
978             // move to start of file
979             reader.seek(0);
980             final var magicStartLength = ASCII_START.length();
981             final var buffer = new byte[magicStartLength];
982             final var n = reader.read(buffer);
983             if (n != magicStartLength) {
984                 throw new LoaderException();
985             }
986             final var str = new String(buffer);
987 
988             isAscii = str.equalsIgnoreCase(ASCII_START);
989             if (isAscii) {
990                 // check
991                 if (reader.isEndOfStream()) {
992                     throw new LoaderException();
993                 }
994                 // load solid name
995                 solidName = reader.readLine();
996             } else {
997                 // Binary format (always is in little endian form)
998 
999                 // set position after 80 byte header
1000                 reader.seek(BINARY_HEADER_SIZE);
1001 
1002                 // read number of triangles
1003                 numberOfTriangles = reader.readUnsignedInt(EndianType.LITTLE_ENDIAN_TYPE);
1004                 numberOfVertices = VERTICES_PER_TRIANGLE * numberOfTriangles;
1005             }
1006         }
1007 
1008     }
1009 }