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  import java.io.OutputStream;
21  
22  /**
23   * Abstract class that defines the interface for writers. A MeshWriter will be
24   * able to transcode a given 3D file into another format.
25   */
26  public abstract class MeshWriter {
27  
28      /**
29       * Loader to load a file to be trans-coded.
30       */
31      protected final Loader loader;
32  
33      /**
34       * Stream where trans-coded data will be written to.
35       */
36      protected final OutputStream stream;
37  
38      /**
39       * Listener to be notified when transcoding process starts, stops or when
40       * progress changes.
41       */
42      protected MeshWriterListener listener;
43  
44      /**
45       * Indicates if mesh writer is locked because a file is being processed.
46       */
47      protected boolean locked;
48  
49      /**
50       * Indicates if texture validation will be ignored or not. If not ignored,
51       * it will be ensured that textures are valid image files.
52       */
53      protected boolean ignoreTextureValidation = false;
54  
55      /**
56       * Internal class implementing specific listeners for some specific loader
57       * implementations.
58       */
59      final Listeners internalListeners;
60  
61      /**
62       * Constructor.
63       *
64       * @param loader loader to load a 3D file.
65       * @param stream stream where trans-coded data will be written to.
66       */
67      protected MeshWriter(final Loader loader, final OutputStream stream) {
68          this.loader = loader;
69          this.stream = stream;
70          listener = null;
71          locked = false;
72  
73          internalListeners = new Listeners(this);
74      }
75  
76      /**
77       * Constructor.
78       *
79       * @param loader   loader to load a 3D file.
80       * @param stream   stream where trans-coded data will be written to.
81       * @param listener listener to be notified of progress changes or when
82       *                 transcoding process starts or finishes.
83       */
84      protected MeshWriter(final Loader loader, final OutputStream stream, final MeshWriterListener listener) {
85          this.loader = loader;
86          this.stream = stream;
87          this.listener = listener;
88          locked = false;
89  
90          internalListeners = new Listeners(this);
91      }
92  
93      /**
94       * Boolean indicating whether this mesh writer is locked because a file is
95       * being processed.
96       *
97       * @return true if this instance is locked, false otherwise.
98       */
99      public boolean isLocked() {
100         return locked;
101     }
102 
103     /**
104      * Returns stream where trans-coded data will be written to.
105      *
106      * @return stream where trans-coded data will be written to.
107      */
108     public OutputStream getStream() {
109         return stream;
110     }
111 
112     /**
113      * Returns listener to be notified when transcoding process starts, stops or
114      * when progress changes.
115      *
116      * @return listener of this mesh writer.
117      */
118     public MeshWriterListener getListener() {
119         return listener;
120     }
121 
122     /**
123      * Sets listener to be notified when transcoding process starts, stops or
124      * when progress changes.
125      *
126      * @param listener listener to be set.
127      * @throws LockedException if this instance is locked because this mesh
128      *                         writer is already processing a file.
129      */
130     public void setListener(final MeshWriterListener listener) throws LockedException {
131         if (isLocked()) {
132             throw new LockedException();
133         }
134         this.listener = listener;
135     }
136 
137     /**
138      * Indicates if this mesh writer is ready because a file and a loader have
139      * been provided.
140      *
141      * @return true if mesh writer is ready, false otherwise.
142      */
143     public boolean isReady() {
144         return (stream != null) && (loader != null);
145     }
146 
147     /**
148      * Internal class implementing listeners for different specific loaders.
149      */
150     private class Listeners implements LoaderListener, LoaderListenerOBJ, LoaderListenerBinary, MaterialLoaderListener {
151 
152         /**
153          * Reference to mesh writer.
154          */
155         private final MeshWriter writer;
156 
157         /**
158          * Constructor.
159          *
160          * @param writer reference to mesh writer.
161          */
162         public Listeners(final MeshWriter writer) {
163             this.writer = writer;
164         }
165 
166         //Loader listener
167 
168         /**
169          * Method called when the loader starts processing a file.
170          *
171          * @param loader reference to loader.
172          */
173         @Override
174         public void onLoadStart(final Loader loader) {
175             //no action needed
176         }
177 
178         /**
179          * Method called when the loader ends processing a file.
180          *
181          * @param loader reference to loader.
182          */
183         @Override
184         public void onLoadEnd(final Loader loader) {
185             //no action needed
186         }
187 
188         /**
189          * Method called when loading progress changes enough to be notified.
190          *
191          * @param loader   reference to loader.
192          * @param progress progress amount as a value between 0.0 and 1.0.
193          */
194         @Override
195         public void onLoadProgressChange(final Loader loader, final float progress) {
196             if (listener != null) {
197                 listener.onWriteProgressChange(writer, progress);
198             }
199         }
200 
201         // OBJ loader listener (requesting a material loader for a given path)
202 
203         /**
204          * Called when an OBJ loader requires a material loader to load the
205          * associated material (MTL) file.
206          *
207          * @param loader reference to loader.
208          * @param path   path where MTL file should be found. It's up to the
209          *               final implementation to determine where the file will be finally
210          *               found.
211          * @return an instance of a material loader.
212          */
213         @Override
214         public MaterialLoaderOBJ onMaterialLoaderRequested(final LoaderOBJ loader, final String path) {
215             if (listener != null) {
216                 final var materialFile = listener.onMaterialFileRequested(writer, path);
217 
218                 if (materialFile == null) {
219                     return null;
220                 }
221 
222                 try {
223                     // when instantiating material loader it checks if file
224                     // exists, if not we return null
225                     return new MaterialLoaderOBJ(materialFile, this);
226                 } catch (final IOException ex) {
227                     return null;
228                 }
229             }
230 
231             return null;
232         }
233 
234         /**
235          * Called when a texture has been found.
236          *
237          * @param loader             reference to loader.
238          * @param textureId          id assigned to the texture that has been found.
239          * @param textureImageWidth  width of texture image that has been found.
240          * @param textureImageHeight height of texture image that has been found.
241          * @return File where texture data will be copied to.
242          */
243         @Override
244         public File onTextureReceived(final LoaderBinary loader, final int textureId, final int textureImageWidth,
245                                       final int textureImageHeight) {
246             if (listener != null) {
247                 return listener.onTextureReceived(writer, textureImageWidth, textureImageHeight);
248             }
249 
250             return null;
251         }
252 
253         /**
254          * Called when texture data is available to be retrieved.
255          *
256          * @param loader             reference to loader.
257          * @param textureFile        reference to a File where texture data has been
258          *                           temporarily copied.
259          * @param textureId          id assigned to the texture.
260          * @param textureImageWidth  width of texture image.
261          * @param textureImageHeight height of texture image.
262          * @return converted image trans-coded into JPG format or resized if
263          * needed, or input textureFile if no changes are needed.
264          */
265         @Override
266         public boolean onTextureDataAvailable(final LoaderBinary loader, final File textureFile, final int textureId,
267                                               final int textureImageWidth, final int textureImageHeight) {
268             var valid = true;
269             var convertedFile = textureFile;
270             if (textureFile != null) {
271                 if (listener != null) {
272                     // notify that texture data has been copied into texture file
273                     // in case it needs to be handled elsewhere (to transform it
274                     // into another format)
275                     convertedFile = listener.onTextureDataAvailable(writer, textureFile, textureImageWidth,
276                             textureImageHeight);
277                 }
278 
279                 if (convertedFile != null) {
280                     final var tex = new Texture(textureId);
281                     tex.setWidth(textureImageWidth);
282                     tex.setHeight(textureImageHeight);
283                     tex.setValid(true);
284 
285                     try {
286                         processTextureFile(tex, convertedFile);
287                     } catch (final IOException ex) {
288                         valid = false;
289                     }
290 
291                     if (listener != null) {
292                         listener.onTextureDataProcessed(writer, textureFile, textureId, textureImageHeight);
293                     }
294                 }
295             }
296             return valid;
297         }
298 
299         // Material loader listener
300 
301         /**
302          * Called when material loader starts processing materials.
303          *
304          * @param loader reference to loader.
305          */
306         @Override
307         public void onLoadStart(final MaterialLoader loader) {
308             // no action required
309         }
310 
311         /**
312          * Called when material loader finishes processing materials.
313          *
314          * @param loader reference to loader.
315          */
316         @Override
317         public void onLoadEnd(final MaterialLoader loader) {
318             // no action required
319         }
320 
321         /**
322          * Called when a texture assigned to a material must be validated to
323          * ensure that texture is valid.
324          *
325          * @param loader  reference to loader.
326          * @param texture texture reference to be validated.
327          * @return true if texture is valid, false otherwise.
328          */
329         @Override
330         public boolean onValidateTexture(final MaterialLoader loader, final Texture texture) {
331             var valid = true;
332             if (listener != null && !ignoreTextureValidation) {
333                 final var textureFile = listener.onValidateTexture(writer, texture);
334                 valid = texture.isValid();
335 
336                 if (valid && textureFile != null) {
337                     try {
338                         processTextureFile(texture, textureFile);
339                     } catch (final IOException ex) {
340                         valid = false;
341                     }
342 
343                     // notify that texture was validated in case that texture
344                     // file needs to be removed
345                     listener.onDidValidateTexture(writer, textureFile);
346                 }
347             }
348             return valid;
349         }
350     }
351 
352     /**
353      * Abstract method to process input file and write it into output stream.
354      *
355      * @throws LoaderException   if 3D file loading fails.
356      * @throws IOException       if an I/O error occurs.
357      * @throws NotReadyException if mesh writer is not ready because either a
358      *                           loader has not been provided or an output stream has not been provided.
359      * @throws LockedException   if this mesh writer is locked processing a file.
360      */
361     public abstract void write() throws LoaderException, IOException, NotReadyException, LockedException;
362 
363     /**
364      * Abstract method to processes texture file. Usually this will imply
365      * validating that image file is not corrupt and has proper size (power of
366      * 2). If not image will be resized.
367      *
368      * @param texture     reference to texture.
369      * @param textureFile file where texture is temporarily copied.
370      * @throws IOException if an I/O error occurs.
371      */
372     protected abstract void processTextureFile(final Texture texture, final File textureFile) throws IOException;
373 }