| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392 |
- //##########################################################################
- //# #
- //# CLOUDCOMPARE #
- //# #
- //# This program is free software; you can redistribute it and/or modify #
- //# it under the terms of the GNU General Public License as published by #
- //# the Free Software Foundation; version 2 or later of the License. #
- //# #
- //# This program is distributed in the hope that it will be useful, #
- //# but WITHOUT ANY WARRANTY; without even the implied warranty of #
- //# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
- //# GNU General Public License for more details. #
- //# #
- //# COPYRIGHT: EDF R&D / TELECOM ParisTech (ENST-TSI) #
- //# #
- //##########################################################################
- #ifndef CC_SERIALIZABLE_OBJECT_HEADER
- #define CC_SERIALIZABLE_OBJECT_HEADER
- //Local
- #include "ccLog.h"
- //CCCoreLib
- #include <CCPlatform.h>
- #include <CCTypes.h>
- //System
- #include <cassert>
- #include <cstdint>
- //Qt
- #include <QDataStream>
- #include <QMultiMap>
- #include <QFile>
- //! Serializable object interface
- class ccSerializableObject
- {
- public:
- //! Destructor
- virtual ~ccSerializableObject() = default;
- //! Returns whether object is serializable of not
- virtual bool isSerializable() const { return false; }
- //! Saves data to binary stream
- /** \param out output file (already opened)
- \param dataVersion target file version
- \return success
- **/
- virtual bool toFile(QFile& out, short dataVersion) const { return false; }
- //! Returns the minimum file version required to save this instance
- /** To be overridden
- \param out output file (already opened)
- \return success
- **/
- virtual short minimumFileVersion() const = 0;
- //! Deserialization flags (bit-field)
- enum DeserializationFlags
- {
- DF_POINT_COORDS_64_BITS = 1, /**< Point coordinates are stored as 64 bits double (otherwise 32 bits floats) **/
- //DGM: inversion is 'historical' ;)
- DF_SCALAR_VAL_32_BITS = 2, /**< Scalar values are stored as 32 bits floats (otherwise 64 bits double) **/
- };
- //! Map of loaded unique IDs (old ID --> new ID)
- typedef QMultiMap<unsigned, unsigned> LoadedIDMap;
- //! Loads data from binary stream
- /** \param in input file (already opened)
- \param dataVersion file version
- \param flags deserialization flags (see ccSerializableObject::DeserializationFlags)
- \param oldToNewIDMap map to link old IDs with new IDs
- \return success
- **/
- virtual bool fromFile(QFile& in, short dataVersion, int flags, LoadedIDMap& oldToNewIDMap) { return false; }
- //! Sends a custom error message (write error) and returns 'false'
- /** Shortcut for returning a standardized error message in the toFile method.
- \return always false
- **/
- static bool WriteError() { ccLog::Error("Write error (disk full or no access right?)"); return false; }
- //! Sends a custom error message (read error) and returns 'false'
- /** Shortcut for returning a standardized error message in the fromFile method.
- \return always false
- **/
- static bool ReadError() { ccLog::Error("Read error (corrupted file or no access right?)"); return false; }
- //! Sends a custom error message (not enough memory) and returns 'false'
- /** Shortcut for returning a standardized error message in the fromFile method.
- \return always false
- **/
- static bool MemoryError() { ccLog::Error("Not enough memory"); return false; }
- //! Sends a custom error message (corrupted file) and returns 'false'
- /** Shortcut for returning a standardized error message in the fromFile method.
- \return always false
- **/
- static bool CorruptError() { ccLog::Error("File seems to be corrupted"); return false; }
- };
- //! Serialization helpers
- class ccSerializationHelper
- {
- public:
- //! Reads one or several 'PointCoordinateType' values from a QDataStream either in float or double format depending on the 'flag' value
- static void CoordsFromDataStream(QDataStream& stream, int flags, PointCoordinateType* out, unsigned count = 1)
- {
- if (flags & ccSerializableObject::DF_POINT_COORDS_64_BITS)
- {
- for (unsigned i = 0; i < count; ++i, ++out)
- {
- double val;
- stream >> val;
- *out = static_cast<PointCoordinateType>(val);
- }
- }
- else
- {
- for (unsigned i = 0; i < count; ++i, ++out)
- {
- float val;
- stream >> val;
- *out = static_cast<PointCoordinateType>(val);
- }
- }
- }
- //! Reads one or several 'ScalarType' values from a QDataStream either in float or double format depending on the 'flag' value
- static void ScalarsFromDataStream(QDataStream& stream, int flags, ScalarType* out, unsigned count = 1)
- {
- if (flags & ccSerializableObject::DF_SCALAR_VAL_32_BITS)
- {
- for (unsigned i = 0; i < count; ++i, ++out)
- {
- float val;
- stream >> val;
- *out = static_cast<PointCoordinateType>(val);
- }
- }
- else
- {
- for (unsigned i = 0; i < count; ++i, ++out)
- {
- double val;
- stream >> val;
- *out = static_cast<PointCoordinateType>(val);
- }
- }
- }
- //! Returns the minimum file version to save/load a 'generic array'
- static short GenericArrayToFileMinVersion() { return 20; }
- //! Helper: saves a vector to file
- /** \param data vector to save (must be allocated)
- \param out output file (must be already opened)
- \return success
- **/
- template <class Type, int N, class ComponentType> static bool GenericArrayToFile(const std::vector<Type>& data, QFile& out)
- {
- assert(out.isOpen() && (out.openMode() & QIODevice::WriteOnly));
-
- //removed to allow saving empty clouds
- //if (data.empty())
- //{
- // return ccSerializableObject::MemoryError();
- //}
- //component count (dataVersion>=20)
- ::uint8_t componentCount = static_cast<::uint8_t>(N);
- if (out.write((const char*)&componentCount, 1) < 0)
- return ccSerializableObject::WriteError();
- //element count = array size (dataVersion>=20)
- ::uint32_t elementCount = static_cast<::uint32_t>(data.size());
- if (out.write((const char*)&elementCount, 4) < 0)
- return ccSerializableObject::WriteError();
- //array data (dataVersion>=20)
- {
- //DGM: do it by chunks, in case it's too big to be processed by the system
- const char* _data = (const char*)data.data();
- qint64 byteCount = static_cast<qint64>(elementCount);
- byteCount *= sizeof(Type);
- while (byteCount != 0)
- {
- static const qint64 s_maxByteSaveCount = (1 << 26); //64 Mb each time
- qint64 saveCount = std::min(byteCount, s_maxByteSaveCount);
- if (out.write(_data, saveCount) < 0)
- return ccSerializableObject::WriteError();
- _data += saveCount;
- byteCount -= saveCount;
- }
- }
- return true;
- }
- //! Helper: loads a vector structure from file
- /** \param data vector to load
- \param in input file (must be already opened)
- \param dataVersion version current data version
- \return success
- **/
- template <class Type, int N, class ComponentType>
- static bool GenericArrayFromFile( std::vector<Type>& data,
- QFile& in,
- short dataVersion,
- const QString& verboseDescription )
- {
- ::uint8_t componentCount = 0;
- ::uint32_t elementCount = 0;
- if (!ReadArrayHeader(in, dataVersion, componentCount, elementCount))
- {
- return false;
- }
- if (componentCount != N)
- {
- return ccSerializableObject::CorruptError();
- }
- ccLog::PrintVerbose(QString("Loading %0: %1 elements and %2 dimension(s)").arg(verboseDescription).arg(elementCount).arg(componentCount));
- if (elementCount)
- {
- //try to allocate memory
- try
- {
- data.resize(elementCount);
- }
- catch (const std::bad_alloc&)
- {
- return ccSerializableObject::MemoryError();
- }
- //array data (dataVersion>=20)
- {
- //Apparently Qt and/or Windows don't like to read too many bytes in a row...
- static const qint64 MaxElementPerChunk = (static_cast<qint64>(1) << 24);
- assert(sizeof(ComponentType) * N == sizeof(Type));
- qint64 byteCount = static_cast<qint64>(data.size()) * (sizeof(ComponentType) * N);
- char* dest = (char*)data.data();
- while (byteCount > 0)
- {
- qint64 chunkSize = std::min(MaxElementPerChunk, byteCount);
- if (in.read(dest, chunkSize) < 0)
- {
- return ccSerializableObject::ReadError();
- }
- byteCount -= chunkSize;
- dest += chunkSize;
- }
- }
- }
- return true;
- }
- //! Helper: loads a vector structure from a file stored with a different type
- /** \param data vector to load
- \param in input file (must be already opened)
- \param dataVersion version current data version
- \param _autoOffset optional: automatic offset to be applied at loading time (based on the first eleemnt)
- \return success
- **/
- template <class Type, int N, class ComponentType, class FileComponentType>
- static bool GenericArrayFromTypedFile( std::vector<Type>& data,
- QFile& in,
- short dataVersion,
- const QString& verboseDescription,
- FileComponentType* _autoOffset = nullptr)
- {
- ::uint8_t componentCount = 0;
- ::uint32_t elementCount = 0;
- if (!ReadArrayHeader(in, dataVersion, componentCount, elementCount))
- {
- return false;
- }
- if (componentCount != N)
- {
- return ccSerializableObject::CorruptError();
- }
- ccLog::PrintVerbose(QString("Loading %0: %1 elements and %2 dimension(s)").arg(verboseDescription).arg(elementCount).arg(componentCount));
- if (elementCount)
- {
- //try to allocate memory
- try
- {
- data.resize(elementCount);
- }
- catch (const std::bad_alloc&)
- {
- return ccSerializableObject::MemoryError();
- }
- //array data (dataVersion>=20)
- //--> sadly we can't read it as a block...
- //we must convert each element, value by value!
- FileComponentType dummyArray[N] { 0 };
- ComponentType* _data = (ComponentType*)data.data();
- size_t elementSize = sizeof(FileComponentType) * N;
- if (_autoOffset)
- {
- //read the first element
- if (in.read((char*)dummyArray, elementSize) >= 0)
- {
- for (unsigned k = 0; k < N; ++k)
- {
- *_autoOffset = dummyArray[k];
- *_data++ = 0;
- }
- }
- else
- {
- return ccSerializableObject::ReadError();
- }
- //read the next elements
- for (unsigned i = 1; i < elementCount; ++i)
- {
- if (in.read((char*)dummyArray, elementSize) >= 0)
- {
- for (unsigned k = 0; k < N; ++k)
- {
- *_data++ = static_cast<ComponentType>(dummyArray[k] - _autoOffset[k]);
- }
- }
- else
- {
- return ccSerializableObject::ReadError();
- }
- }
- }
- else
- {
- // no automatic offset
- for (unsigned i = 0; i < elementCount; ++i)
- {
- if (in.read((char*)dummyArray, sizeof(FileComponentType) * N) >= 0)
- {
- for (unsigned k = 0; k < N; ++k)
- {
- *_data++ = static_cast<ComponentType>(dummyArray[k]);
- }
- }
- else
- {
- return ccSerializableObject::ReadError();
- }
- }
- }
- }
- return true;
- }
- protected:
- static bool ReadArrayHeader(QFile& in,
- short dataVersion,
- ::uint8_t &componentCount,
- ::uint32_t &elementCount)
- {
- assert(in.isOpen() && (in.openMode() & QIODevice::ReadOnly));
- if (dataVersion < 20)
- return ccSerializableObject::CorruptError();
- //component count (dataVersion>=20)
- if (in.read((char*)&componentCount, 1) < 0)
- return ccSerializableObject::ReadError();
- //element count = array size (dataVersion>=20)
- if (in.read((char*)&elementCount, 4) < 0)
- return ccSerializableObject::ReadError();
- return true;
- }
- };
- #endif //CC_SERIALIZABLE_OBJECT_HEADER
|