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 }