1 /*
2  * The following code is slightly based on the C# application
3  * SynthExport (http://synthexport.codeplex.com/) by Christoph Hausner
4  * and on the C++ application
5  * PhotoSynthToolkit (http://www.visual-experiments.com/) by Henri Astre
6  */
7 
8 #include "synthData.h"
9 #include <math.h>
10 #include <QtSoapMessage>
11 #include <QNetworkAccessManager>
12 #include <QNetworkRequest>
13 #include <QNetworkReply>
14 #include <QScriptEngine>
15 #include <QScriptValue>
16 #include <QScriptValueIterator>
17 #include <QImage>
18 #include <QFile>
19 
20 /********************
21  * CameraParameters *
22  ********************/
23 
getTranslation()24 Point3m CameraParameters::getTranslation()
25 {
26   return Point3m(_fields[CameraParameters::POS_X], _fields[CameraParameters::POS_Z], -_fields[CameraParameters::POS_Y]);
27 }
28 
getRotation()29 Matrix44m CameraParameters::getRotation()
30 {
31   qreal a = _fields[CameraParameters::ROT_X] * _fields[CameraParameters::ROT_X] +
32             _fields[CameraParameters::ROT_Y] * _fields[CameraParameters::ROT_Y] +
33             _fields[CameraParameters::ROT_Z] * _fields[CameraParameters::ROT_Z];
34   qreal w2 = 1 - a;
35   qreal w = sqrt(w2);
36   Quaternion<CMeshO::ScalarType> q(w,_fields[CameraParameters::ROT_X],_fields[CameraParameters::ROT_Y],_fields[CameraParameters::ROT_Z]);
37   Matrix44m rot;
38   q.ToMatrix(rot);
39   Matrix44m flip;
40   flip.SetRotateDeg(180,Point3m(1,0,0));
41   // (rot * flip)^T = flip^T * rot^T
42   Matrix44m transposedFlippedRot = flip.transpose() * rot.transpose();
43 
44   Matrix44m rotate90;
45   rotate90.SetRotateDeg(90,Point3m(1,0,0));
46   Matrix44m rotation = transposedFlippedRot * rotate90;
47 
48   return rotation;
49 }
50 
51 /**************
52  * PointCloud *
53  **************/
54 
PointCloud(int coordSysID,int binFileCount,QObject * parent)55 PointCloud::PointCloud(int coordSysID, int binFileCount, QObject *parent)
56   : QObject(parent)
57 {
58   _coordinateSystem = coordSysID;
59   _binFileCount = binFileCount;
60 }
61 
62 /********************
63  * CoordinateSystem *
64  ********************/
65 
CoordinateSystem(int id,QObject * parent)66 CoordinateSystem::CoordinateSystem(int id, QObject *parent)
67   : QObject(parent)
68 {
69   _id = id;
70   _shouldBeImported = false;
71   _pointCloud = 0;
72 }
73 
74 /*************
75  * SynthData *
76  *************/
77 
78 //contains the error descriptions relative to the errors that may occurr during the import process
79 //each position index in the array corresponds to the same value in the enum Errors of SynthData class.
80 //These strings are used by applyFilter in case of error.
81 const QString SynthData::errors[] =
82 {
83   "The provided URL is invalid",
84   "Save path is missing: specify one",
85   "The web service returned an error",
86   "The requested Synth is unavailable",
87   "Could not parse web service response: unexpected response",
88   "This filter is compatible with photosynths belonging to \"Synth\" category only",
89   "Error parsing collection data",
90   "This synth is empty",
91   "Error reading binary data, file may be corrupted",
92   "The point cloud is stored in an incompatible format and cannot be loaded",
93   "Error creating output directory for images"
94   "Error saving images to the filesystem"
95 };
96 
97 const char *SynthData::steps[] =
98 {
99   "Contacting web service...",
100   "Downloading json data...",
101   "Parsing json data...",
102   "Downloading point cloud bin files...",
103   "Loading point cloud data...",
104   "Downloading images..."
105 };
106 
checkAndSetState(bool condition,Error errorCode,QNetworkReply * httpResponse)107 bool SynthData::checkAndSetState(bool condition, Error errorCode, QNetworkReply *httpResponse)
108 {
109   if(condition)
110     setState(errorCode, httpResponse);
111   return condition;
112 }
113 
setState(Error errorCode,QNetworkReply * httpResponse)114 void SynthData::setState(Error errorCode, QNetworkReply *httpResponse)
115 {
116   _state = errorCode;
117   _mutex.lock();
118   _dataReady = true;
119   _mutex.unlock();
120   if(httpResponse)
121     httpResponse->deleteLater();
122 }
123 
SynthData(ImportSettings & settings,QObject * parent)124 SynthData::SynthData(ImportSettings &settings, QObject *parent)
125   : QObject(parent)
126 {
127   _coordinateSystems = new QList<CoordinateSystem*>();
128   _imageMap = new QHash<int,Image>();
129   _settings = settings;
130   _state = PENDING;
131   _step = WEB_SERVICE;
132   _progress = 0;
133   _mutex.lock();
134   _dataReady = false;
135   _mutex.unlock();
136   _semaphore = 0;
137   _imagesToDownloadCount = 0;
138 }
139 
~SynthData()140 SynthData::~SynthData()
141 {
142   delete _coordinateSystems;
143   delete _imageMap;
144 }
145 
146 /*
147  * Returns true if this SynthData represents a Synth downloaded from photosynth.net.
148  * Upon a successful download this function returns true if called on the download operation result.
149  * Use this function to determine if the data were successfully downloaded.
150  */
isValid()151 bool SynthData::isValid()
152 {
153   return (_state == SYNTH_NO_ERROR);
154 }
155 
156 /*
157  * Useful for cb().
158  * Returns the progress for the current step, and sets info according to the step being executed
159  */
progressInfo()160 int SynthData::progressInfo()
161 {
162   _info = steps[_step];
163   return _progress;
164 }
165 
166 /*
167  * Contacts the photosynth web service to retrieve informations about
168  * the synth whose identifier is contained within the given url.
169  */
downloadSynthInfo(vcg::CallBackPos * cb)170 void SynthData::downloadSynthInfo(vcg::CallBackPos *cb)
171 {
172   _cb = cb;
173   _step = WEB_SERVICE;
174   _progress = 0;
175   _cb(progressInfo(),_info.toStdString().data());
176   if(_settings._url.isNull() || _settings._url.isEmpty())
177   {
178     _state = WRONG_URL;
179     _mutex.lock();
180     _dataReady = true;
181     _mutex.unlock();
182     return;
183   }
184 
185   if(_settings._imageSavePath.isNull())
186   {
187     _state = WRONG_PATH;
188     _mutex.lock();
189     _dataReady = true;
190     _mutex.unlock();
191     return ;
192   }
193   _savePath = _settings._imageSavePath;
194 
195   //extracts the synth identifier
196   int i = _settings._url.indexOf("cid=",0,Qt::CaseInsensitive);
197   if(i < 0 || _settings._url.length() < i + 40)
198   {
199     _state = WRONG_URL;
200     _mutex.lock();
201     _dataReady = true;
202     _mutex.unlock();
203     return;
204   }
205 
206   QString cid = _settings._url.mid(i + 4, 36);
207   _collectionID = cid;
208 
209   QtSoapMessage message;
210   message.setMethod("GetCollectionData", "http://labs.live.com/");
211   message.addMethodArgument("collectionId", "", cid);
212   message.addMethodArgument("incrementEmbedCount", "", false, 0);
213 
214   QtSoapHttpTransport *transport = new QtSoapHttpTransport(this);
215   connect(transport, SIGNAL(responseReady(const QtSoapMessage &)),this, SLOT(readWSresponse(const QtSoapMessage &)));
216   transport->setAction("http://labs.live.com/GetCollectionData");
217   transport->setHost("photosynth.net");
218 
219   transport->submitRequest(message, "/photosynthws/PhotosynthService.asmx");
220   _state = PENDING;
221   _progress = 50;
222   _cb(progressInfo(),_info.toStdString().data());
223 }
224 
225 /*
226  * Handles the photosynth web service response.
227  */
readWSresponse(const QtSoapMessage & response)228 void SynthData::readWSresponse(const QtSoapMessage &response)
229 {
230   if(checkAndSetState(response.isFault(), WEBSERVICE_ERROR)) return;
231   const QtSoapType &returnValue = response.returnValue();
232   if(returnValue["Result"].isValid())
233   {
234     //the requested synth was found
235     if(returnValue["Result"].toString() == "OK")
236     {
237       //we can only extract point clouds from synths
238       if(returnValue["CollectionType"].toString() == "Synth")
239       {
240         _collectionRoot = returnValue["CollectionRoot"].toString();
241         //the url of the json string containing data about the synth coordinate systems (different clusters of points)
242         //their camera parameters and the number of binary files containing the point clouds data.
243         QString jsonURL = returnValue["JsonUrl"].toString();
244         _progress = 100;
245         _cb(progressInfo(),_info.toStdString().data());
246         downloadJsonData(jsonURL);
247       }
248       else
249         setState(WRONG_COLLECTION_TYPE);
250     }
251     else
252       setState(NEGATIVE_RESPONSE);
253   }
254   else
255     setState(UNEXPECTED_RESPONSE);
256 }
257 
258 /*
259  * Performs an HTTP request to download a json string containing data
260  * about the synth coordinate systems (different clusters of points)
261  * their camera parameters and the number of binary files containing
262  * the point clouds data.
263  */
downloadJsonData(QString jsonURL)264 void SynthData::downloadJsonData(QString jsonURL)
265 {
266   _step = DOWNLOAD_JSON;
267   _progress = 0;
268   _cb(progressInfo(),_info.toStdString().data());
269   QNetworkAccessManager *manager = new QNetworkAccessManager(this);
270   connect(manager, SIGNAL(finished(QNetworkReply*)),
271           this, SLOT(parseJsonString(QNetworkReply*)));
272 
273   manager->get(QNetworkRequest(QUrl(jsonURL)));
274   _progress = 50;
275   _cb(progressInfo(),_info.toStdString().data());
276 }
277 
278 /*
279  * Extracts from the given string informations about the coordinate
280  * systems and their camera parameters.
281  */
parseJsonString(QNetworkReply * httpResponse)282 void SynthData::parseJsonString(QNetworkReply *httpResponse)
283 {
284   _progress = 100;
285   _cb(progressInfo(),_info.toStdString().data());
286   _step = PARSE_JSON;
287   _progress = 0;
288   _cb(progressInfo(),_info.toStdString().data());
289   QByteArray payload = httpResponse->readAll();
290   QString json(payload);
291   QScriptEngine engine;
292   QScriptValue jsonData = engine.evaluate("(" + json + ")");
293   if(checkAndSetState(engine.hasUncaughtException(), JSON_PARSING, httpResponse)) return;
294   //the "l" property contains an object whose name is the synth cid
295   //and whose value is an array containing the coordinate systems
296   QScriptValue collections = jsonData.property("l");
297   if(checkAndSetState(!collections.isValid(), JSON_PARSING, httpResponse)) return;
298   //use an iterator to retrieve the first collection
299   QScriptValueIterator iterator(collections);
300   QScriptValue collection;
301   int coordSystemsCount = 0;
302   if(iterator.hasNext())
303   {
304     iterator.next();
305     collection = iterator.value();
306     coordSystemsCount = collection.property("_num_coord_systems").toInt32();
307     _numImages = collection.property("_num_images").toInt32();
308   }
309   else
310   {
311     setState(JSON_PARSING, httpResponse);
312     return;
313   }
314   if(coordSystemsCount > 0)
315   {
316     //the json object containing the images informations
317     QScriptValue map = collection.property("image_map");
318     parseImageMap(map);
319     CoordinateSystem *coordSys;
320     //the coordinate systems list
321     QScriptValue coordSystems = collection.property("x");
322     for(int i = 0; i <= coordSystemsCount; ++i)
323     {
324       if(_settings._clusterID == -1 || i == _settings._clusterID)
325       {
326         _progress = 50 + (i / (2*coordSystemsCount) * 100);
327         QScriptValue cs = coordSystems.property(QString::number(i));
328         QScriptValue pointCloud = cs.property("k");
329         if(pointCloud.isValid() && !pointCloud.isNull())
330         {
331           //NOTE: we are only interested in coordinate systems having a valid point cloud
332           //so we create one only under this condition, and discard all other ones
333           coordSys = new CoordinateSystem(i,this);
334           coordSys->_shouldBeImported = true;
335           _coordinateSystems->append(coordSys);
336           QScriptValueIterator it(pointCloud);
337           if(it.hasNext())
338           {
339             //pointCloud[0]
340             it.next();
341             QString str = it.value().toString();
342             if(!str.isNull() && !str.isEmpty())
343             {
344               //pointCloud[1]
345               it.next();
346               int binFileCount = it.value().toInt32();
347               PointCloud *p = new PointCloud(i,binFileCount,coordSys);
348               coordSys->_pointCloud = p;
349             }
350           }
351           //list of cameras
352           //NOTE: we are only interested in camera lists belonging to a coordinate system having a valid point cloud
353           //this is the reason why the code below is inside the previous if branch
354           QScriptValue cameras = cs.property("r");
355           if(cameras.isValid() && !cameras.isNull())
356           {
357             QScriptValueIterator it(cameras);
358             while(it.hasNext())
359             {
360               it.next();
361               int id = it.name().toInt();
362               QScriptValue camera = it.value();
363               //contains the camera extrinsics and some of intrinsics
364               QScriptValue parameters = camera.property("j");
365               CameraParameters params;
366               params._camID = id;
367               QScriptValueIterator paramIt(parameters);
368               paramIt.next();
369               params._imageID = paramIt.value().toInt32();
370               Image img = _imageMap->value(params._imageID);
371               img._shouldBeDownloaded = img._shouldBeDownloaded + 1;
372               _imageMap->insert(img._ID,img);
373               _imagesToDownloadCount++;
374               for(int i = CameraParameters::FIRST; i <= CameraParameters::LAST; ++i)
375               {
376                 paramIt.next();
377                 params[i] = paramIt.value().toNumber();
378               }
379               QScriptValue distortion = camera.property("f");
380               if(distortion.isValid() && !distortion.isNull())
381               {
382                 QScriptValueIterator distortionIt(distortion);
383                 distortionIt.next();
384                 params._distortionRadius1 = distortionIt.value().toNumber();
385                 distortionIt.next();
386                 params._distortionRadius2 = distortionIt.value().toNumber();
387               }
388               else
389                 params._distortionRadius1 = params._distortionRadius2 = 0;
390               coordSys->_cameraParametersList.append(params);
391             }
392           }
393         }
394       }
395     }
396     _progress = 100;
397     _cb(progressInfo(),_info.toStdString().data());
398     downloadBinFiles();
399   }
400   else
401     setState(EMPTY);
402   httpResponse->deleteLater();
403 }
404 
405 /*
406  * Parse the image list and, for each element of the list, creates an Image and puts it in the dictionary
407  */
parseImageMap(QScriptValue & map)408 void SynthData::parseImageMap(QScriptValue &map)
409 {
410   QScriptValueIterator imageIt(map);
411   int i = 0;
412   while(imageIt.hasNext())
413   {
414     _progress = i / (2*_numImages) * 100;
415     _cb(progressInfo(),_info.toStdString().data());
416     imageIt.next();
417     Image image;
418     image._shouldBeDownloaded = 0;
419     image._ID = imageIt.name().toInt();
420     QScriptValue size = imageIt.value().property("d");
421     QScriptValueIterator sizeIt(size);
422     sizeIt.next();
423     image._width = sizeIt.value().toInt32();
424     sizeIt.next();
425     image._height = sizeIt.value().toInt32();
426     image._url = imageIt.value().property("u").toString();
427     _imageMap->insert(image._ID,image);
428     ++i;
429   }
430 }
431 
432 /*
433  * Asks the server for bin files containing point clouds data
434  * sending an http request for each bin file composing the synth
435  */
downloadBinFiles()436 void SynthData::downloadBinFiles()
437 {
438   _step = DOWNLOAD_BIN;
439   _progress = 0;
440   _cb(progressInfo(),_info.toStdString().data());
441   QNetworkAccessManager *manager = new QNetworkAccessManager(this);
442   connect(manager, SIGNAL(finished(QNetworkReply*)),
443           this, SLOT(loadBinFile(QNetworkReply*)));
444   foreach(CoordinateSystem *sys, *_coordinateSystems)
445   {
446     if(sys->_shouldBeImported && sys->_pointCloud)
447     {
448       //As QNetworkAccessManager API is asynchronous, there is no mean, in the slot handling the response (loadBinFile),
449       //to understand if the response received is the last one, and thus to know if all data have been received.
450       //To let loadBinFile know when the processed response is the last one (allowing the _dataReady variable to be set to true)
451       //the counter _semaphore is used
452       _mutex.lock();
453       _semaphore += sys->_pointCloud->_binFileCount;
454       _mutex.unlock();
455       for(int i = 0; i < sys->_pointCloud->_binFileCount; ++i)
456       {
457         QString url = QString("%0points_%1_%2.bin").arg(_collectionRoot).arg(sys->_id).arg(i);
458         QNetworkRequest *request = new QNetworkRequest(QUrl(url));
459         PointCloud *p = (PointCloud *)sys->_pointCloud;
460         //the slot handling the response (loadBinFile) is able to know which pointCloud the received data belong to
461         //retrieving the originating object of the request whose response is being processed
462         request->setOriginatingObject(p);
463         manager->get(*request);
464         delete request;
465       }
466     }
467   }
468   _totalBinFilesCount = _semaphore;
469   if(_totalBinFilesCount == 0)
470   {
471     _state = SYNTH_NO_ERROR;
472     _mutex.lock();
473     _dataReady = true;
474     _mutex.unlock();
475   }
476 }
477 
478 /*
479  * Reads point data from httpResponse payload and fills
480  * the pointCloud of the coordinateSystem relative to the http request
481  * originating httpResponse
482  */
loadBinFile(QNetworkReply * httpResponse)483 void SynthData::loadBinFile(QNetworkReply *httpResponse)
484 {
485   //if(_state == READING_BIN_DATA || _state == BIN_DATA_FORMAT)
486   bool ignore = false;
487   _mutex.lock();
488   ignore = _dataReady;
489   _mutex.unlock();
490   if(ignore)
491   {
492     httpResponse->deleteLater();
493     return;
494   }
495 
496   _step = LOADING_BIN;
497   _progress = (_totalBinFilesCount - _semaphore) / _totalBinFilesCount * 100;
498   _cb(progressInfo(),_info.toStdString().data());
499   bool error = false;
500   unsigned short versionMajor = readBigEndianUInt16(httpResponse,error);
501   if(checkAndSetState(error,READING_BIN_DATA,httpResponse))
502     return;
503   unsigned short versionMinor = readBigEndianUInt16(httpResponse,error);
504   if(checkAndSetState(error,READING_BIN_DATA,httpResponse)) return;
505   if(checkAndSetState(versionMajor != 1 || versionMinor != 0, BIN_DATA_FORMAT, httpResponse)) return;
506   int n = readCompressedInt(httpResponse,error);
507   if(checkAndSetState(error,READING_BIN_DATA,httpResponse)) return;
508   //skip the header section
509   for (int i = 0; i < n; i++)
510   {
511     int m = readCompressedInt(httpResponse,error);
512     if(checkAndSetState(error,READING_BIN_DATA,httpResponse)) return;
513     for (int j = 0; j < m; j++)
514     {
515       readCompressedInt(httpResponse,error);
516       if(checkAndSetState(error,READING_BIN_DATA,httpResponse)) return;
517       readCompressedInt(httpResponse,error);
518       if(checkAndSetState(error,READING_BIN_DATA,httpResponse)) return;
519     }
520   }
521 
522   int nPoints = readCompressedInt(httpResponse,error);
523   if(checkAndSetState(error,READING_BIN_DATA,httpResponse)) return;
524   for (int i = 0; i < nPoints; i++)
525   {
526     Point point;
527 
528     point._x = readBigEndianSingle(httpResponse,error);
529     if(checkAndSetState(error,READING_BIN_DATA,httpResponse)) return;
530     point._y = readBigEndianSingle(httpResponse,error);
531     if(checkAndSetState(error,READING_BIN_DATA,httpResponse)) return;
532     point._z = readBigEndianSingle(httpResponse,error);
533     if(checkAndSetState(error,READING_BIN_DATA,httpResponse)) return;
534 
535     ushort color = readBigEndianUInt16(httpResponse,error);
536     if(checkAndSetState(error,READING_BIN_DATA,httpResponse)) return;
537 
538     point._r = (uchar)(((color >> 11) * 255) / 31);
539     point._g = (uchar)((((color >> 5) & 63) * 255) / 63);
540     point._b = (uchar)(((color & 31) * 255) / 31);
541     //the cloud whose received points belong to
542     PointCloud *cloud = (PointCloud *)httpResponse->request().originatingObject();
543     cloud->_points.append(point);
544   }
545 
546   _mutex.lock();
547   --_semaphore;
548   _mutex.unlock();
549   if(_semaphore == 0)
550   {
551     if(!_savePath.isEmpty())
552     {
553       _progress = 100;
554       _cb(progressInfo(),_info.toStdString().data());
555       downloadImages();
556     }
557     else
558       setState(SYNTH_NO_ERROR);
559   }
560 
561   httpResponse->deleteLater();
562 }
563 
564 /*
565  * Asks the server for the images making up this synth.
566  * One http request is made for each image.
567  */
downloadImages()568 void SynthData::downloadImages()
569 {
570   _step = DOWNLOAD_IMG;
571   _progress = 0;
572   _cb(progressInfo(),_info.toStdString().data());
573   QDir dir(_savePath);
574   dir.mkdir(_collectionID);
575 
576   QNetworkAccessManager *manager = new QNetworkAccessManager(this);
577   connect(manager, SIGNAL(finished(QNetworkReply*)),
578           this, SLOT(saveImages(QNetworkReply*)));
579   int requestCount = 0;
580   foreach(Image img, *_imageMap)
581   {
582     for(int i = 0; i < img._shouldBeDownloaded; ++i)
583     {
584       QNetworkRequest *request = new QNetworkRequest(QUrl(img._url));
585       request->setAttribute(QNetworkRequest::User,QVariant(img._ID));
586       manager->get(*request);
587       delete request;
588       ++requestCount;
589     }
590   }
591   if(requestCount == 0)
592   {
593     _state = SYNTH_NO_ERROR;
594     _mutex.lock();
595     _dataReady = true;
596     _mutex.unlock();
597   }
598 }
599 
600 /*
601  * Reads the http response and save the image to the filesystem
602  */
saveImages(QNetworkReply * httpResponse)603 void SynthData::saveImages(QNetworkReply *httpResponse)
604 {
605   //if(_state == SAVE_IMG)
606   bool ignore = false;
607   _mutex.lock();
608   ignore = _dataReady;
609   _mutex.unlock();
610   if(ignore)
611   {
612     httpResponse->deleteLater();
613     return;
614   }
615 
616   if(httpResponse->error() != QNetworkReply::NoError)
617     qDebug() << httpResponse->errorString();
618 
619   _progress = (int)(_semaphore / _numImages) * 100;
620   _cb(progressInfo(),_info.toStdString().data());
621   QByteArray payload = httpResponse->readAll();
622   QDir dir(_savePath);
623   dir.cd(_collectionID);
624   int id = httpResponse->request().attribute(QNetworkRequest::User).toInt();
625   QString filename("IMG_%1.jpg");
626   QFile file(dir.filePath(filename.arg(QString::number(id))));
627   if(checkAndSetState(!file.open(QIODevice::WriteOnly), SAVE_IMG, httpResponse)) return;
628   if(checkAndSetState(file.write(payload) == -1, SAVE_IMG, httpResponse)) return;
629   file.close();
630 
631   _mutex.lock();
632   ++_semaphore;
633   _mutex.unlock();
634   if(checkAndSetState(_semaphore == _imagesToDownloadCount, SYNTH_NO_ERROR, httpResponse)) return;
635 
636   httpResponse->deleteLater();
637 }
638 
639 /******************
640  * ImportSettings *
641  ******************/
642 
ImportSettings(QString url,int clusterID,QString imageSavePath)643 ImportSettings::ImportSettings(QString url, int clusterID, QString imageSavePath)
644 {
645   _url = url;
646   _clusterID = clusterID;
647   _imageSavePath = imageSavePath;
648 }
649 
650 /*********************
651  * Utility functions *
652  *********************/
653 
654 /*
655  * based on C# code from Christoph Hausner's SynthExport
656  */
readCompressedInt(QIODevice * device,bool & error)657 int readCompressedInt(QIODevice *device, bool &error)
658 {
659   error = false;
660   int i = 0;
661   unsigned char byte;
662 
663   do
664   {
665     error = device->read((char *)&byte, sizeof(char)) == -1 ? true : false;
666     if(error)
667       return i;
668     i = (i << 7) | (byte & 127);
669   } while (byte < 128);
670   return i;
671 }
672 
673 /*
674  * based on C# code from Christoph Hausner's SynthExport
675  */
readBigEndianSingle(QIODevice * device,bool & error)676 float readBigEndianSingle(QIODevice *device, bool &error)
677 {
678   error = false;
679   unsigned char bytes[4];
680   for(int i = 0; i < 4; ++i)
681   {
682     error = device->read((char *)(bytes + i), sizeof(char)) == -1 ? true : false;
683     if(error)
684       return -1;
685   }
686   unsigned char reversed[] = { bytes[3],bytes[2],bytes[1],bytes[0] };
687 
688   float *f = (float *)(&  reversed[0]);
689   return*f;
690 }
691 
692 /*
693  * based on C# code from Christoph Hausner's SynthExport
694  */
readBigEndianUInt16(QIODevice * device,bool & error)695 unsigned short readBigEndianUInt16(QIODevice *device, bool &error)
696 {
697   error = false;
698   unsigned short byte1=0;
699   error = device->read((char *)&byte1,sizeof(char)) == -1 ? true : false;
700   if(error) return 0;
701   unsigned short byte2=0;
702   error = device->read((char *)&byte2,sizeof(char)) == -1 ? true : false;
703   if(error)
704     return 0;
705 
706   return byte2 | (byte1 << 8);
707 }
708 
printPoint(Point * p)709 void printPoint(Point *p)
710 {
711   qDebug() << "x =" << p->_x << "; y =" << p->_y << "; z =" << p->_z << "R: " << p->_r << " G: " << p->_g << " B: " << p->_b;
712 }
713