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.BufferedOutputStream;
19  import java.io.DataOutputStream;
20  import java.io.File;
21  import java.io.IOException;
22  import java.io.OutputStream;
23  import java.nio.file.Files;
24  
25  /**
26   * Reads a 3D object and converts it into custom binary format.
27   */
28  public class MeshWriterBinary extends MeshWriter {
29  
30      /**
31       * Version of the binary file supported by this class.
32       */
33      public static final byte VERSION = 2;
34  
35      /**
36       * Buffer size to write data into output stream.
37       */
38      public static final int BUFFER_SIZE = 1024;
39  
40      /**
41       * Stream to write binary data to output.
42       */
43      private DataOutputStream dataStream;
44  
45      /**
46       * Constructor.
47       *
48       * @param loader loader to load a 3D file.
49       * @param stream stream where trans-coded data will be written to.
50       */
51      public MeshWriterBinary(final Loader loader, final OutputStream stream) {
52          super(loader, stream);
53      }
54  
55      /**
56       * Constructor.
57       *
58       * @param loader   loader to load a 3D file.
59       * @param stream   stream where trans-coded data will be written to.
60       * @param listener listener to be notified of progress changes or when
61       *                 transcoding process starts or finishes.
62       */
63      public MeshWriterBinary(final Loader loader, final OutputStream stream, final MeshWriterListener listener) {
64          super(loader, stream, listener);
65      }
66  
67      /**
68       * Processes input file provided to loader and writes it trans-coded into
69       * output stream.
70       *
71       * @throws LoaderException   if 3D file loading fails.
72       * @throws IOException       if an I/O error occurs.
73       * @throws NotReadyException if mesh writer is not ready because either a
74       *                           loader has not been provided or an output stream has not been provided.
75       * @throws LockedException   if this mesh writer is locked processing a file.
76       */
77      @SuppressWarnings("DuplicatedCode")
78      @Override
79      public void write() throws LoaderException, IOException, NotReadyException, LockedException {
80  
81          if (!isReady()) {
82              throw new NotReadyException();
83          }
84          if (isLocked()) {
85              throw new LockedException();
86          }
87  
88          try {
89              dataStream = new DataOutputStream(new BufferedOutputStream(stream));
90  
91              locked = true;
92              if (listener != null) {
93                  listener.onWriteStart(this);
94              }
95  
96              loader.setListener(this.internalListeners);
97  
98              // write version
99              dataStream.writeByte(VERSION);
100 
101             final var iter = loader.load();
102 
103             var minX = Float.MAX_VALUE;
104             var minY = Float.MAX_VALUE;
105             var minZ = Float.MAX_VALUE;
106             var maxX = -Float.MAX_VALUE;
107             var maxY = -Float.MAX_VALUE;
108             var maxZ = -Float.MAX_VALUE;
109 
110             while (iter.hasNext()) {
111                 final var chunk = iter.next();
112                 if (listener != null) {
113                     listener.onChunkAvailable(this, chunk);
114                 }
115 
116                 final var coords = chunk.getVerticesCoordinatesData();
117                 final var colors = chunk.getColorData();
118                 final var indices = chunk.getIndicesData();
119                 final var textureCoords = chunk.getTextureCoordinatesData();
120                 final var normals = chunk.getNormalsData();
121                 final var colorComponents = chunk.getColorComponents();
122 
123                 final var coordsAvailable = (coords != null);
124                 final var colorsAvailable = (colors != null);
125                 final var indicesAvailable = (indices != null);
126                 final var textureCoordsAvailable = (textureCoords != null);
127                 final var normalsAvailable = (normals != null);
128 
129                 if (chunk.getMinX() < minX) {
130                     minX = chunk.getMinX();
131                 }
132                 if (chunk.getMinY() < minY) {
133                     minY = chunk.getMinY();
134                 }
135                 if (chunk.getMinZ() < minZ) {
136                     minZ = chunk.getMinZ();
137                 }
138 
139                 if (chunk.getMaxX() > maxX) {
140                     maxX = chunk.getMaxX();
141                 }
142                 if (chunk.getMaxY() > maxY) {
143                     maxY = chunk.getMaxY();
144                 }
145                 if (chunk.getMaxZ() > maxZ) {
146                     maxZ = chunk.getMaxZ();
147                 }
148 
149                 // compute size of material in bytes
150                 final var material = chunk.getMaterial();
151                 // boolean indicating availability
152                 // of material
153                 var materialSizeInBytes = 1;
154 
155                 if (material != null) {
156                     // material id (int)
157                     materialSizeInBytes += Integer.SIZE / 8;
158                     // ambient color (RGB) -> 3 bytes
159 
160                     //boolean indicating availability
161                     materialSizeInBytes += 1;
162                     if (material.isAmbientColorAvailable()) {
163                         // RGB components
164                         materialSizeInBytes += 3;
165                     }
166 
167                     // diffuse color
168 
169                     // boolean indicating availability
170                     materialSizeInBytes += 1;
171                     if (material.isDiffuseColorAvailable()) {
172                         // RGB components
173                         materialSizeInBytes += 3;
174                     }
175 
176                     // specular color
177 
178                     // boolean indicating availability
179                     materialSizeInBytes += 1;
180                     if (material.isSpecularColorAvailable()) {
181                         // RGB components
182                         materialSizeInBytes += 3;
183                     }
184 
185                     // specular coefficient (float)
186 
187                     // boolean indicating availability
188                     materialSizeInBytes += 1;
189                     if (material.isSpecularCoefficientAvailable()) {
190                         materialSizeInBytes += Float.SIZE / 8;
191                     }
192 
193                     // ambient texture map
194 
195                     // boolean indicating availability
196                     materialSizeInBytes += 1;
197                     if (material.isAmbientTextureMapAvailable()) {
198                         // id of texture (int)
199                         materialSizeInBytes += Integer.SIZE / 8;
200                         // texture width (int)
201                         materialSizeInBytes += Integer.SIZE / 8;
202                         // texture height (int)
203                         materialSizeInBytes += Integer.SIZE / 8;
204                     }
205 
206                     // diffuse texture map
207 
208                     // boolean indicating availability
209                     materialSizeInBytes += 1;
210                     if (material.isDiffuseTextureMapAvailable()) {
211                         // id of texture (int)
212                         materialSizeInBytes += Integer.SIZE / 8;
213                         // texture width (int)
214                         materialSizeInBytes += Integer.SIZE / 8;
215                         // texture height (int)
216                         materialSizeInBytes += Integer.SIZE / 8;
217                     }
218 
219                     // specular texture map
220 
221                     // boolean indicating availability
222                     materialSizeInBytes += 1;
223                     if (material.isSpecularTextureMapAvailable()) {
224                         // id of texture (int)
225                         materialSizeInBytes += Integer.SIZE / 8;
226                         // texture width (int)
227                         materialSizeInBytes += Integer.SIZE / 8;
228                         // texture height (int)
229                         materialSizeInBytes += Integer.SIZE / 8;
230                     }
231 
232                     // alpha texture map
233 
234                     // boolean indicating availability
235                     materialSizeInBytes += 1;
236                     if (material.isAlphaTextureMapAvailable()) {
237                         // id of texture (int)
238                         materialSizeInBytes += Integer.SIZE / 8;
239                         // texture width (int)
240                         materialSizeInBytes += Integer.SIZE / 8;
241                         // texture height (int)
242                         materialSizeInBytes += Integer.SIZE / 8;
243                     }
244 
245                     // bump texture map
246 
247                     // boolean indicating availability
248                     materialSizeInBytes += 1;
249                     if (material.isBumpTextureMapAvailable()) {
250                         // id of texture (int)
251                         materialSizeInBytes += Integer.SIZE / 8;
252                         // texture width (int)
253                         materialSizeInBytes += Integer.SIZE / 8;
254                         // texture height (int)
255                         materialSizeInBytes += Integer.SIZE / 8;
256                     }
257 
258                     // transparency
259 
260                     // availability
261                     materialSizeInBytes += 1;
262                     if (material.isTransparencyAvailable()) {
263                         // one byte 0-255 of
264                         // transparency
265                         materialSizeInBytes += 1;
266                     }
267 
268                     // enum of illumination(int)
269                     // boolean containing availability
270                     materialSizeInBytes += 1;
271                     if (material.isIlluminationAvailable()) {
272                         materialSizeInBytes += Integer.SIZE / 8;
273                     }
274                 }
275 
276                 // compute size of chunk data in bytes
277                 var coordsSizeInBytes = 0;
278                 var colorsSizeInBytes = 0;
279                 var indicesSizeInBytes = 0;
280                 var textureCoordsSizeInBytes = 0;
281                 var normalsSizeInBytes = 0;
282 
283                 if (coordsAvailable) {
284                     coordsSizeInBytes = coords.length * Float.SIZE / 8;
285                 }
286                 if (colorsAvailable) {
287                     colorsSizeInBytes = colors.length;
288                 }
289                 if (indicesAvailable) {
290                     indicesSizeInBytes = indices.length * Short.SIZE / 8;
291                 }
292                 if (textureCoordsAvailable) {
293                     textureCoordsSizeInBytes = textureCoords.length * Float.SIZE / 8;
294                 }
295                 if (normalsAvailable) {
296                     normalsSizeInBytes = normals.length * Float.SIZE / 8;
297                 }
298 
299                 int chunkSize = materialSizeInBytes + coordsSizeInBytes + colorsSizeInBytes + indicesSizeInBytes
300                         + textureCoordsSizeInBytes + normalsSizeInBytes
301                         + (5 * Integer.SIZE / 8) + // sizes
302                         (6 * Float.SIZE / 8); // min/max values
303                 if (colorsAvailable) {
304                     // bytes for number of color components
305                     chunkSize += Integer.SIZE / 8;
306                 }
307 
308                 // indicate that no more textures follow
309                 if (!ignoreTextureValidation) {
310                     // byte below is written only once after all textures and
311                     // before all vertex data
312                     dataStream.writeBoolean(false);
313 
314                     // so that no more textures are
315                     // written into stream
316                     ignoreTextureValidation = true;
317                 }
318 
319                 // write total chunk size
320                 dataStream.writeInt(chunkSize);
321 
322                 // indicate material availability
323                 dataStream.writeBoolean(material != null);
324                 if (material != null) {
325                     // material id
326                     dataStream.writeInt(material.getId());
327 
328                     // ambient color
329                     dataStream.writeBoolean(material.isAmbientColorAvailable());
330                     if (material.isAmbientColorAvailable()) {
331                         var b = (byte) (material.getAmbientRedColor() & 0x00ff);
332                         dataStream.writeByte(b);
333                         b = (byte) (material.getAmbientGreenColor() & 0x00ff);
334                         dataStream.writeByte(b);
335                         b = (byte) (material.getAmbientBlueColor() & 0x00ff);
336                         dataStream.writeByte(b);
337                     }
338 
339                     // diffuse color
340                     dataStream.writeBoolean(material.isDiffuseColorAvailable());
341                     if (material.isDiffuseColorAvailable()) {
342                         var b = (byte) (material.getDiffuseRedColor() & 0x00ff);
343                         dataStream.writeByte(b);
344                         b = (byte) (material.getDiffuseGreenColor() & 0x00ff);
345                         dataStream.writeByte(b);
346                         b = (byte) (material.getDiffuseBlueColor() & 0x00ff);
347                         dataStream.writeByte(b);
348                     }
349 
350                     // specular color
351                     dataStream.writeBoolean(material.isSpecularColorAvailable());
352                     if (material.isSpecularColorAvailable()) {
353                         var b = (byte) (material.getSpecularRedColor() & 0x00ff);
354                         dataStream.writeByte(b);
355                         b = (byte) (material.getSpecularGreenColor() & 0x00ff);
356                         dataStream.writeByte(b);
357                         b = (byte) (material.getSpecularBlueColor() & 0x00ff);
358                         dataStream.writeByte(b);
359                     }
360 
361                     // specular coefficient (float)
362                     dataStream.writeBoolean(material.isSpecularCoefficientAvailable());
363                     if (material.isSpecularCoefficientAvailable()) {
364                         dataStream.writeFloat(material.getSpecularCoefficient());
365                     }
366 
367                     // ambient texture map
368                     dataStream.writeBoolean(material.isAmbientTextureMapAvailable());
369                     if (material.isAmbientTextureMapAvailable()) {
370                         final var tex = material.getAmbientTextureMap();
371                         // texture id
372                         dataStream.writeInt(tex.getId());
373                         // texture width
374                         dataStream.writeInt(tex.getWidth());
375                         // texture height
376                         dataStream.writeInt(tex.getHeight());
377                     }
378 
379                     // diffuse texture map
380                     dataStream.writeBoolean(material.isDiffuseTextureMapAvailable());
381                     if (material.isDiffuseTextureMapAvailable()) {
382                         final var tex = material.getDiffuseTextureMap();
383                         // texture id
384                         dataStream.writeInt(tex.getId());
385                         // texture width
386                         dataStream.writeInt(tex.getWidth());
387                         // texture height
388                         dataStream.writeInt(tex.getHeight());
389                     }
390 
391                     // specular texture map
392                     dataStream.writeBoolean(material.isSpecularTextureMapAvailable());
393                     if (material.isSpecularTextureMapAvailable()) {
394                         final var tex = material.getSpecularTextureMap();
395                         // texture id
396                         dataStream.writeInt(tex.getId());
397                         // texture width
398                         dataStream.writeInt(tex.getWidth());
399                         // texture height
400                         dataStream.writeInt(tex.getHeight());
401                     }
402 
403                     // alpha texture map
404                     dataStream.writeBoolean(material.isAlphaTextureMapAvailable());
405                     if (material.isAlphaTextureMapAvailable()) {
406                         final var tex = material.getAlphaTextureMap();
407                         // texture id
408                         dataStream.writeInt(tex.getId());
409                         // texture width
410                         dataStream.writeInt(tex.getWidth());
411                         // texture height
412                         dataStream.writeInt(tex.getHeight());
413                     }
414 
415                     // bump texture map
416                     dataStream.writeBoolean(material.isBumpTextureMapAvailable());
417                     if (material.isBumpTextureMapAvailable()) {
418                         final var tex = material.getBumpTextureMap();
419                         // texture id
420                         dataStream.writeInt(tex.getId());
421                         // texture width
422                         dataStream.writeInt(tex.getWidth());
423                         // texture height
424                         dataStream.writeInt(tex.getHeight());
425                     }
426 
427                     dataStream.writeBoolean(material.isTransparencyAvailable());
428                     if (material.isTransparencyAvailable()) {
429                         final var b = (byte) (material.getTransparency() & 0x00ff);
430                         dataStream.writeByte(b);
431                     }
432 
433                     dataStream.writeBoolean(material.isIlluminationAvailable());
434                     if (material.isIlluminationAvailable()) {
435                         dataStream.writeInt(material.getIllumination().value());
436                     }
437                 }
438 
439                 // write coords size
440                 dataStream.writeInt(coordsSizeInBytes);
441                 // write coords
442                 if (coordsAvailable) {
443                     for (final var coord : coords) {
444                         dataStream.writeFloat(coord);
445                     }
446                 }
447 
448                 // write colors size
449                 dataStream.writeInt(colorsSizeInBytes);
450                 // write colors
451                 if (colorsAvailable) {
452                     var i = 0;
453                     while (i < colors.length) {
454                         final var b = (byte) (colors[i] & 0x00ff);
455                         dataStream.writeByte(b);
456                         i++;
457                     }
458                     dataStream.writeInt(colorComponents);
459                 }
460 
461                 // write indices size
462                 dataStream.writeInt(indicesSizeInBytes);
463                 // write indices
464                 if (indicesAvailable) {
465                     for (final var index : indices) {
466                         final var s = (short) (index & 0x0000ffff);
467                         dataStream.writeShort(s);
468                     }
469                 }
470 
471                 // write texture coords
472                 dataStream.writeInt(textureCoordsSizeInBytes);
473                 // write texture coords
474                 if (textureCoordsAvailable) {
475                     for (final var textureCoord : textureCoords) {
476                         dataStream.writeFloat(textureCoord);
477                     }
478                 }
479 
480                 // write normals
481                 dataStream.writeInt(normalsSizeInBytes);
482                 // write normals
483                 if (normalsAvailable) {
484                     for (final var normal : normals) {
485                         dataStream.writeFloat(normal);
486                     }
487                 }
488 
489                 // write min/max x, y, z
490                 dataStream.writeFloat(chunk.getMinX());
491                 dataStream.writeFloat(chunk.getMinY());
492                 dataStream.writeFloat(chunk.getMinZ());
493 
494                 dataStream.writeFloat(chunk.getMaxX());
495                 dataStream.writeFloat(chunk.getMaxY());
496                 dataStream.writeFloat(chunk.getMaxZ());
497 
498                 dataStream.flush();
499             }
500 
501             if (listener != null) {
502                 listener.onWriteEnd(this);
503             }
504             locked = false;
505 
506         } catch (final LoaderException | IOException e) {
507             throw e;
508         } catch (final Exception e) {
509             throw new LoaderException(e);
510         }
511     }
512 
513     /**
514      * Processes texture file. By reading provided texture file that has been
515      * created in a temporal location and embedding it into resulting output
516      * stream.
517      *
518      * @param texture     reference to texture that uses texture image.
519      * @param textureFile file containing texture image. File will usually be
520      *                    created in a temporal location.
521      * @throws IOException if an I/O error occurs.
522      */
523     @Override
524     protected void processTextureFile(final Texture texture, final File textureFile) throws IOException {
525         if (!textureFile.exists()) {
526             return;
527         }
528 
529         // write boolean to true indicating that a texture follows
530         dataStream.writeBoolean(true);
531 
532         // write int containing texture id
533         final var textureId = texture.getId();
534         dataStream.writeInt(textureId);
535 
536         // write int containing image width
537         final var width = texture.getWidth();
538         dataStream.writeInt(width);
539 
540         // write int containing image height
541         final var height = texture.getHeight();
542         dataStream.writeInt(height);
543 
544         // write length of texture file in bytes
545         final var length = textureFile.length();
546         dataStream.writeLong(length);
547 
548         // write file data
549         try (final var textureStream = Files.newInputStream(textureFile.toPath())) {
550             final var buffer = new byte[BUFFER_SIZE];
551             int n;
552             while ((n = textureStream.read(buffer)) > 0) {
553                 dataStream.write(buffer, 0, n);
554             }
555             dataStream.flush();
556         }
557     }
558 }