/*
 * E57Simple - public header of E57 Simple API for reading/writing .e57 files.
 *
 * Copyright (c) 2010 Stan Coleby (scoleby@intelisum.com)
 * Copyright (c) 2020 PTC Inc.
 * Copyright (c) 2022 Andy Maloney <asmaloney@gmail.com>
 *
 * Permission is hereby granted, free of charge, to any person or organization
 * obtaining a copy of the software and accompanying documentation covered by
 * this license (the "Software") to use, reproduce, display, distribute,
 * execute, and transmit the Software, and to prepare derivative works of the
 * Software, and to permit third-parties to whom the Software is furnished to
 * do so, all subject to the following:
 *
 * The copyright notices in the Software and this entire statement, including
 * the above license grant, this restriction and the following disclaimer,
 * must be included in all copies of the Software, in whole or in part, and
 * all derivative works of the Software, unless such copies or derivative
 * works are solely in the form of machine-executable object code generated by
 * a source language processor.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
 * SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
 * FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 */

#pragma once

/// @file
/// @brief Data structures for E57 Simple API

#include "E57Format.h"

namespace e57
{
   /// @cond documentNonPublic The following isn't part of the API, and isn't documented.
   class ReaderImpl;
   class WriterImpl;
   /// @endcond

   /// @brief Defines a rigid body translation in Cartesian coordinates.
   struct E57_DLL Translation
   {
      /// The X coordinate of the translation (in meters)
      double x = 0.0;

      /// The Y coordinate of the translation (in meters)
      double y = 0.0;

      /// The Z coordinate of the translation (in meters)
      double z = 0.0;

      bool operator==( const Translation &rhs ) const
      {
         return ( x == rhs.x ) && ( y == rhs.y ) && ( z == rhs.z );
      }

      bool operator!=( const Translation &rhs ) const
      {
         return !operator==( rhs );
      }

      /// @brief Return "no translation".
      /// @returns A translation of (0.0, 0.0, 0.0).
      static Translation identity()
      {
         return {};
      }
   };

   /// @brief Represents a rigid body rotation.
   struct E57_DLL Quaternion
   {
      /// The real part of the quaternion (shall be nonnegative)
      double w = 1.0;

      /// The i coefficient of the quaternion
      double x = 0.0;

      /// The j coefficient of the quaternion
      double y = 0.0;

      /// The k coefficient of the quaternion
      double z = 0.0;

      bool operator==( const Quaternion &rhs ) const
      {
         return ( w == rhs.w ) && ( x == rhs.x ) && ( y == rhs.y ) && ( z == rhs.z );
      }

      bool operator!=( const Quaternion &rhs ) const
      {
         return !operator==( rhs );
      }

      /// @brief Return the identity quaternion.
      /// @returns A quaternion of (1.0, 0.0, 0.0, 0.0).
      static Quaternion identity()
      {
         return {};
      }
   };

   /// @brief Defines a rigid body transform in cartesian coordinates.
   struct E57_DLL RigidBodyTransform
   {
      /// A unit quaternion representing the rotation, R, of the transform
      Quaternion rotation;

      /// The translation point vector, t, of the transform
      Translation translation;

      bool operator==( const RigidBodyTransform &rhs ) const
      {
         return ( rotation == rhs.rotation ) && ( translation == rhs.translation );
      }

      bool operator!=( const RigidBodyTransform &rhs ) const
      {
         return !operator==( rhs );
      }

      /// @brief Returns a RigidBodyTransform without rotation or translation.
      static RigidBodyTransform identity()
      {
         return { Quaternion::identity(), Translation::identity() };
      }
   };

   /// @brief Specifies an axis-aligned box in local cartesian coordinates.
   struct E57_DLL CartesianBounds
   {
      /// The minimum extent of the bounding box in the X direction
      double xMinimum = -DOUBLE_MAX;
      /// The maximum extent of the bounding box in the X direction
      double xMaximum = DOUBLE_MAX;

      /// The minimum extent of the bounding box in the Y direction
      double yMinimum = -DOUBLE_MAX;
      /// The maximum extent of the bounding box in the Y direction
      double yMaximum = DOUBLE_MAX;

      /// The minimum extent of the bounding box in the Z direction
      double zMinimum = -DOUBLE_MAX;
      /// The maximum extent of the bounding box in the Z direction
      double zMaximum = DOUBLE_MAX;

      bool operator==( const CartesianBounds &rhs ) const
      {
         return ( xMinimum == rhs.xMinimum ) && ( xMaximum == rhs.xMaximum ) &&
                ( yMinimum == rhs.yMinimum ) && ( yMaximum == rhs.yMaximum ) &&
                ( zMinimum == rhs.zMinimum ) && ( zMaximum == rhs.zMaximum );
      }

      bool operator!=( const CartesianBounds &rhs ) const
      {
         return !operator==( rhs );
      }
   };

   /// @brief Stores the bounds of some data in spherical coordinates.
   struct E57_DLL SphericalBounds
   {
      SphericalBounds(); // constructor in the cpp to avoid exposing M_PI

      /// The minimum extent of the bounding region in the r direction
      double rangeMinimum;
      /// The maximum extent of the bounding region in the r direction
      double rangeMaximum;

      /// The minimum extent of the bounding region from the horizontal plane
      double elevationMinimum;
      /// The maximum extent of the bounding region from the horizontal plane
      double elevationMaximum;

      /// The starting azimuth angle defining the extent of the bounding region around the z axis
      double azimuthStart;
      /// The ending azimuth angle defining the extent of the bounding region  around the z axis
      double azimuthEnd;

      bool operator==( const SphericalBounds &rhs ) const
      {
         return ( rangeMinimum == rhs.rangeMinimum ) && ( rangeMaximum == rhs.rangeMaximum ) &&
                ( elevationMinimum == rhs.elevationMinimum ) &&
                ( elevationMaximum == rhs.elevationMaximum ) &&
                ( azimuthStart == rhs.azimuthStart ) && ( azimuthEnd == rhs.azimuthEnd );
      }

      bool operator!=( const SphericalBounds &rhs ) const
      {
         return !operator==( rhs );
      }
   };

   /// @brief Stores the minimum and maximum of rowIndex, columnIndex, and returnIndex fields for a
   /// set of points.
   struct E57_DLL IndexBounds
   {
      /// The minimum rowIndex value of any point represented by this IndexBounds object
      int64_t rowMinimum = 0;
      /// The maximum rowIndex value of any point represented by this IndexBounds object
      int64_t rowMaximum = 0;

      /// The minimum columnIndex value of any point represented by this IndexBounds object
      int64_t columnMinimum = 0;
      /// The maximum columnIndex value of any point represented by this IndexBounds object
      int64_t columnMaximum = 0;

      /// The minimum returnIndex value of any point represented by this IndexBounds object
      int64_t returnMinimum = 0;
      /// The maximum returnIndex value of any point represented by this IndexBounds object
      int64_t returnMaximum = 0;

      bool operator==( const IndexBounds &rhs ) const
      {
         return ( rowMinimum == rhs.rowMinimum ) && ( rowMaximum == rhs.rowMaximum ) &&
                ( columnMinimum == rhs.columnMinimum ) && ( columnMaximum == rhs.columnMaximum ) &&
                ( returnMinimum == rhs.returnMinimum ) && ( returnMaximum == rhs.returnMaximum );
      }

      bool operator!=( const IndexBounds &rhs ) const
      {
         return !operator==( rhs );
      }
   };

   /// @brief Specifies the limits for the value of signal intensity that a sensor is capable of
   /// producing
   struct E57_DLL IntensityLimits
   {
      /// The minimum producible intensity value. Unit is unspecified.
      double intensityMinimum = 0.0;
      /// The maximum producible intensity value. Unit is unspecified.
      double intensityMaximum = 0.0;

      bool operator==( const IntensityLimits &rhs ) const
      {
         return ( intensityMinimum == rhs.intensityMinimum ) &&
                ( intensityMaximum == rhs.intensityMaximum );
      }

      bool operator!=( const IntensityLimits &rhs ) const
      {
         return !operator==( rhs );
      }
   };

   /// @brief Specifies the limits for the value of red, green, and blue color that a sensor is
   /// capable of producing.
   struct E57_DLL ColorLimits
   {
      /// The minimum producible red color value. Unit is unspecified.
      double colorRedMinimum = 0.0;
      /// The maximum producible red color value. Unit is unspecified.
      double colorRedMaximum = 0.0;

      /// The minimum producible green color value. Unit is unspecified.
      double colorGreenMinimum = 0.0;
      /// The maximum producible green color value. Unit is unspecified.
      double colorGreenMaximum = 0.0;

      /// The minimum producible blue color value. Unit is unspecified.
      double colorBlueMinimum = 0.0;
      /// The maximum producible blue color value. Unit is unspecified.
      double colorBlueMaximum = 0.0;

      bool operator==( const ColorLimits &rhs ) const
      {
         return ( colorRedMinimum == rhs.colorRedMinimum ) &&
                ( colorRedMaximum == rhs.colorRedMaximum ) &&
                ( colorGreenMinimum == rhs.colorGreenMinimum ) &&
                ( colorGreenMaximum == rhs.colorGreenMaximum ) &&
                ( colorBlueMinimum == rhs.colorBlueMinimum ) &&
                ( colorBlueMaximum == rhs.colorBlueMaximum );
      }

      bool operator!=( const ColorLimits &rhs ) const
      {
         return !operator==( rhs );
      }
   };

   /// @brief Encodes date and time.
   /// @details The date and time is encoded using a single floating point number, stored as an E57
   /// Float element which is based on the Global Positioning System (GPS) time scale.
   struct E57_DLL DateTime
   {
      /// @brief The time, in seconds, since GPS time was zero.
      /// @details This time specification may include fractions of a second.
      double dateTimeValue = 0.0;

      /// @brief This element should be present, and its value set to 1 if, and only if, the time
      /// stored in the dateTimeValue element is obtained from an atomic clock time source.
      /// @details Shall be either 0 or 1.
      int32_t isAtomicClockReferenced = 0;

      bool operator==( const DateTime &rhs ) const
      {
         return ( dateTimeValue == rhs.dateTimeValue ) &&
                ( isAtomicClockReferenced == rhs.isAtomicClockReferenced );
      }

      bool operator!=( const DateTime &rhs ) const
      {
         return !operator==( rhs );
      }
   };

   /// @brief Stores the top-level information for the XML section of the file.
   struct E57_DLL E57Root
   {
      /// Must contain the string "ASTM E57 3D Imaging Data File"
      ustring formatName;

      /// A globally unique identification string for the current version of the file
      ustring guid;

      /// Major version number (should be 1)
      uint32_t versionMajor = 1;
      /// Minor version number (should be 0)
      uint32_t versionMinor = 0;

      /// The version identifier for the E57 file format library which wrote the file.
      ustring e57LibraryVersion;

      /// Date/time that the file was created
      DateTime creationDateTime;

      /// Size of the Data3D vector for storing 3D imaging data
      int64_t data3DSize = 0;

      /// Size of the Images2D vector for storing 2D images from a camera or similar device.
      int64_t images2DSize = 0;

      /// Information describing the Coordinate Reference System to be used for the file
      ustring coordinateMetadata;
   };

   /// @brief Stores information about a single group of points in a row or column
   struct E57_DLL LineGroupRecord
   {
      /// @brief The value of the identifying element of all members in this group.
      /// @details Shall be in the interval [0, 2^63).
      int64_t idElementValue = 0;

      /// @brief The record number of the first point in the continuous interval.
      /// @details Shall be in the interval [0, 2^63).
      int64_t startPointIndex = 0;

      /// @brief The number of PointRecords in the group.
      /// @details Shall be in the interval [1, 2^63). May be zero.
      int64_t pointCount = 0;

      /// @brief The bounding box (in Cartesian coordinates) of all points in the group.
      /// @details These are in the local coordinate system of the points.
      CartesianBounds cartesianBounds;

      /// @brief The bounding region (in spherical coordinates) of all the points in the group.
      /// @details These are in the local coordinate system of the points.
      SphericalBounds sphericalBounds;
   };

   /// @brief Stores a set of point groups organized by the rowIndex or columnIndex attribute of the
   /// PointRecord
   struct E57_DLL GroupingByLine
   {
      /// @brief The name of the PointRecord element that identifies which group the point is in.
      /// @details The value of this string must be "rowIndex" or "columnIndex".
      ustring idElementName;

      /// @brief Size of the groups compressedVector of LineGroupRecord structures.
      int64_t groupsSize = 0;

      /// @brief The size value for the LineGroupRecord::pointCount.
      int64_t pointCountSize = 0;
   };

   /// @brief Supports the division of points within an Data3D into logical groupings
   struct E57_DLL PointGroupingSchemes
   {
      /// @brief Grouping information by row or column index
      GroupingByLine groupingByLine;
   };

   /// @brief Used to set the type of node in some PointStandardizedFieldsAvailable fields.
   enum class NumericalNodeType
   {
      Integer = 0,   ///< Use IntegerNode
      ScaledInteger, ///< Use ScaledIntegerNode
      Float,         ///< Use FloatNode with floats
      Double,        ///< Use FloatNode with doubles
   };

   /// @brief Used to interrogate if standardized fields are available
   struct E57_DLL PointStandardizedFieldsAvailable
   {
      /// Indicates that the PointRecord cartesianX field is active
      bool cartesianXField = false;
      /// Indicates that the PointRecord cartesianY field is active
      bool cartesianYField = false;
      /// Indicates that the PointRecord cartesianZ field is active
      bool cartesianZField = false;
      /// Indicates that the PointRecord cartesianInvalidState field is active
      bool cartesianInvalidStateField = false;

      /// Indicates that the PointRecord sphericalRange field is active
      bool sphericalRangeField = false;
      /// Indicates that the PointRecord sphericalAzimuth field is active
      bool sphericalAzimuthField = false;
      /// Indicates that the PointRecord sphericalElevation field is active
      bool sphericalElevationField = false;
      /// Indicates that the PointRecord sphericalInvalidState field is active
      bool sphericalInvalidStateField = false;

      /// @brief Indicates that the PointRecord cartesian and range fields should be configured with
      /// this minimum value e.g. E57_FLOAT_MIN or E57_DOUBLE_MIN.
      /// @details If using a ScaledIntegerNode then this needs to be a minimum range value.
      double pointRangeMinimum = DOUBLE_MIN;

      /// @brief Indicates that the PointRecord cartesian and range fields should be configured with
      /// this maximum value e.g. E57_FLOAT_MAX or E57_DOUBLE_MAX.
      /// @details If using a ScaledIntegerNode then this needs to be a maximum range value.
      double pointRangeMaximum = DOUBLE_MAX;

      /// @brief Controls the type of Node used for the PointRecord cartesian and range fields
      /// @details Accepts NumericalNodeType::ScaledInteger, NumericalNodeType::Float, and
      /// NumericalNodeType::Double.
      NumericalNodeType pointRangeNodeType = NumericalNodeType::Float;

      /// @brief Sets the scale if using scaled integers for point fields
      /// @details If pointRangeNodeType == NumericalNodeType::ScaledInteger, it will use this value
      /// to scale the numbers and it must be > 0.0.
      double pointRangeScale = 0.0;

      /// @brief Indicates that the PointRecord angle fields should be configured with this minimum
      /// value E57_FLOAT_MIN or E57_DOUBLE_MIN.
      /// @details If using a ScaledIntegerNode then this needs to be a minimum angle value.
      double angleMinimum = DOUBLE_MIN;

      /// @brief Indicates that the PointRecord angle fields should be configured with this maximum
      /// value e.g. E57_FLOAT_MAX or E57_DOUBLE_MAX.
      /// @details If using a ScaledIntegerNode then this needs to be a maximum angle value.
      double angleMaximum = DOUBLE_MAX;

      /// @brief Controls the type of Node used for the PointRecord angle fields
      /// @details Accepts NumericalNodeType::ScaledInteger, NumericalNodeType::Float, and
      /// NumericalNodeType::Double.
      NumericalNodeType angleNodeType = NumericalNodeType::Float;

      /// @brief Sets the scale if using scaled integers for angle fields
      /// @details If angleNodeType == NumericalNodeType::ScaledInteger, it will use this value
      /// to scale the numbers and it must be > 0.0.
      double angleScale = 0.0;

      /// Indicates that the PointRecord @a rowIndex field is active
      bool rowIndexField = false;

      /// Indicates that the PointRecord @a rowIndex fields should be configured with this maximum
      /// value where the minimum will be set to 0.
      uint32_t rowIndexMaximum = UINT32_MAX;

      /// Indicates that the PointRecord @a columnIndex field is active
      bool columnIndexField = false;

      /// Indicates that the PointRecord @a columnIndex fields should be configured with this
      /// maximum value where the minimum will be set to 0.
      uint32_t columnIndexMaximum = UINT32_MAX;

      /// Indicates that the PointRecord @a returnIndex field is active
      bool returnIndexField = false;
      /// Indicates that the PointRecord @a returnCount field is active
      bool returnCountField = false;
      /// Indicates that the PointRecord return fields should be configured  with this maximum value
      /// where the minimum will be set to 0.
      uint8_t returnMaximum = UINT8_MAX;

      /// Indicates that the PointRecord @a timeStamp field is active
      bool timeStampField = false;
      /// Indicates that the PointRecord @a isTimeStampInvalid field is active
      bool isTimeStampInvalidField = false;

      /// @brief Indicates that the PointRecord @a timeStamp fields should be configured with this
      /// minimum value e.g. E57_UINT32_MIN, E57_DOUBLE_MIN or E57_DOUBLE_MIN.
      /// @details If using a ScaledIntegerNode then this needs to be a minimum time value.
      double timeMinimum = DOUBLE_MIN;

      /// Indicates that the PointRecord @a timeStamp fields should be configured with this maximum
      /// value. e.g. E57_UINT32_MAX, E57_DOUBLE_MAX or E57_DOUBLE_MAX.
      double timeMaximum = DOUBLE_MAX;

      /// @brief Controls the type of Node used for the PointRecord time fields
      /// @details Accepts NumericalNodeType::Integer, NumericalNodeType::ScaledInteger,
      /// NumericalNodeType::Float, and NumericalNodeType::Double.
      NumericalNodeType timeNodeType = NumericalNodeType::Float;

      /// @brief Sets the scale if using scaled integers for time fields
      /// @details If timeNodeType == NumericalNodeType::ScaledInteger, it will use this value
      /// to scale the numbers and it must be > 0.0.
      double timeScale = 0.0;

      /// Indicates that the PointRecord @a intensity field is active
      bool intensityField = false;
      /// Indicates that the PointRecord @a isIntensityInvalid field is active
      bool isIntensityInvalidField = false;

      /// @brief Controls the type of Node used for the PointRecord intensity fields
      /// @details Accepts NumericalNodeType::Integer, NumericalNodeType::ScaledInteger,
      /// NumericalNodeType::Float, and NumericalNodeType::Double.
      NumericalNodeType intensityNodeType = NumericalNodeType::Float;

      /// @brief Sets the scale if using scaled integers for intensity fields
      /// @details If intensityNodeType == NumericalNodeType::ScaledInteger, it will use this value
      /// to scale the numbers and it must be > 0.0.
      double intensityScale = 0.0;

      /// Indicates that the PointRecord @a colorRed field is active
      bool colorRedField = false;
      /// Indicates that the PointRecord @a colorGreen field is active
      bool colorGreenField = false;
      /// Indicates that the PointRecord @a colorBlue field is active
      bool colorBlueField = false;
      /// Indicates that the PointRecord @a isColorInvalid field is active
      bool isColorInvalidField = false;

      /// Indicates that the PointRecord @a nor:normalX field is active
      bool normalXField = false;
      /// Indicates that the PointRecord @a nor:normalY field is active
      bool normalYField = false;
      /// Indicates that the PointRecord @a nor:normalZ field is active
      bool normalZField = false;
   };

   /// @brief Stores the top-level information for a single lidar scan
   struct E57_DLL Data3D
   {
      /// A user-defined name for the Data3D.
      ustring name;

      /// A globally unique identification string for the current version of the  Data3D object
      ustring guid;

      /// @brief A vector of globally unique identification Strings from which the points in this
      /// Data3D originated.
      std::vector<ustring> originalGuids;

      /// A user-defined description of the Image
      ustring description;

      /// The name of the manufacturer for the sensor used to collect the  points in this Data3D.
      ustring sensorVendor;
      /// The model name or number for the sensor.
      ustring sensorModel;
      /// The serial number for the sensor.
      ustring sensorSerialNumber;
      /// The version number for the sensor hardware at the time of data collection.
      ustring sensorHardwareVersion;
      /// The version number for the software used for the data  collection.
      ustring sensorSoftwareVersion;
      /// @brief The version number for the firmware installed in the sensor at the time of data
      /// collection.
      ustring sensorFirmwareVersion;

      /// @brief The ambient temperature, measured at the sensor, at the time of data collection.
      /// @details This units are degrees Celsius.
      float temperature = FLOAT_MAX;

      /// @brief The percentage relative humidity, measured at the sensor, at the time of data
      /// collection.
      /// @details Shall be in the interval [0, 100].
      float relativeHumidity = FLOAT_MAX;

      /// @brief The atmospheric pressure, measured at the sensor, at the time of data collection
      /// @details The units are Pascals. Shall be positive.
      float atmosphericPressure = FLOAT_MAX;

      /// The start date and time that the data was acquired.
      DateTime acquisitionStart;
      /// The end date and time that the data was acquired.
      DateTime acquisitionEnd;

      /// @brief A rigid body transform that describes the coordinate frame of the 3D imaging system
      /// origin.
      /// @details These are in the file-level coordinate system.
      RigidBodyTransform pose;

      /// The bounds of the row, column, and return number of all the  points in this Data3D.
      IndexBounds indexBounds;

      /// @brief The bounding region (in cartesian coordinates) of all the points in this Data3D.
      /// @details These are in the local coordinate system of the points.
      CartesianBounds cartesianBounds;

      /// @brief The bounding region (in spherical coordinates) of all the points in this Data3D.
      /// @details These are in the local coordinate system of the points.
      SphericalBounds sphericalBounds;

      /// The limits for the value of signal intensity that the sensor is capable of producing.
      IntensityLimits intensityLimits;

      /// @brief The limits for the value of red, green, and blue color that the sensor is capable
      /// of producing.
      ColorLimits colorLimits;

      /// The defined schemes that group points in different ways
      PointGroupingSchemes pointGroupingSchemes;
      /// The active fields used in the WritePoints function.
      PointStandardizedFieldsAvailable pointFields;

      /// The number of points in the Data3D.
      /// On 32-bit systems size_t will allow for 4,294,967,295 points per scan which seems
      /// reasonable...
      size_t pointCount = 0;
   };

   /// @brief Stores pointers to user-provided buffers
   template <typename COORDTYPE> struct Data3DPointsData_t
   {
      static_assert( std::is_floating_point<COORDTYPE>::value, "Floating point type required." );

      /// @brief Default constructor does not manage any memory, adjust min/max for floats, or
      /// validate data.
      Data3DPointsData_t() = default;

      /*!
      @brief Constructor which allocates buffers for all valid fields in the given Data3D header.

      @details
      This constructor will also adjust the min/max fields in the data3D pointFields if
      we are using floats, and run some validation on the Data3D.

      @param [in] data3D Completed header which indicates the fields we are using

      @throw ::ErrorValueOutOfBounds (n/c)
      @throw ::ErrorInvalidNodeType (n/c)
      */
      explicit Data3DPointsData_t( e57::Data3D &data3D );

      /// @brief Destructor will delete any memory allocated using the Data3DPointsData_t( const
      /// e57::Data3D & ) constructor
      ~Data3DPointsData_t();

      /// @brief Pointer to a buffer with the X coordinate (in meters) of the point in Cartesian
      /// coordinates
      COORDTYPE *cartesianX = nullptr;

      /// @brief Pointer to a buffer with the Y coordinate (in meters) of the point in Cartesian
      /// coordinates
      COORDTYPE *cartesianY = nullptr;

      /// @brief Pointer to a buffer with the Z coordinate (in meters) of the point in Cartesian
      /// coordinates
      COORDTYPE *cartesianZ = nullptr;

      /// @brief Value = 0 if the point is considered valid, 1 otherwise
      int8_t *cartesianInvalidState = nullptr;

      /// @brief Pointer to a buffer with the Point response intensity. Unit is unspecified.
      double *intensity = nullptr;

      /// @brief Value = 0 if the intensity is considered valid, 1 otherwise
      int8_t *isIntensityInvalid = nullptr;

      /// @brief Pointer to a buffer with the Red color coefficient. Unit is unspecified
      uint16_t *colorRed = nullptr;
      /// @brief Pointer to a buffer with the Green color coefficient. Unit is unspecified
      uint16_t *colorGreen = nullptr;
      /// @brief Pointer to a buffer with the Blue color coefficient. Unit is unspecified
      uint16_t *colorBlue = nullptr;
      /// @brief Value = 0 if the color is considered valid, 1 otherwise
      int8_t *isColorInvalid = nullptr;

      /// @brief Pointer to a buffer with the range (in meters) of points in spherical coordinates.
      COORDTYPE *sphericalRange = nullptr;

      /// @brief Pointer to a buffer with the Azimuth angle (in radians) of point in spherical
      /// coordinates
      COORDTYPE *sphericalAzimuth = nullptr;

      /// @brief Pointer to a buffer with the Elevation angle (in radians) of point in spherical
      /// coordinates
      COORDTYPE *sphericalElevation = nullptr;

      /// @brief Value = 0 if the range is considered valid, 1 otherwise
      int8_t *sphericalInvalidState = nullptr;

      /// @brief Pointer to a buffer with the row number of point (zero based).
      /// @details This is useful for data that is stored in a regular grid. Shall be in the
      /// interval (0, 2^31).
      int32_t *rowIndex = nullptr;

      /// @brief Pointer to a buffer with the column number of point (zero based).
      /// @details This is useful for data that is stored in a regular grid. Shall be in the
      /// interval (0, 2^31).
      int32_t *columnIndex = nullptr;

      /// @brief Pointer to a buffer with the number of this return (zero based).
      /// @details That is, 0 is the first return, 1 is the second, and so on. Shall be in the
      /// interval (0, returnCount). Only for multi-return sensors.
      int8_t *returnIndex = nullptr;

      /// @brief Pointer to a buffer with the total number of returns for the pulse that this
      /// corresponds to.
      /// @details Shall be in the interval (0, 2^7). Only for multi-return sensors.
      int8_t *returnCount = nullptr;

      /// @brief Pointer to a buffer with the time (in seconds) since the start time for the data.
      /// @details This is given by acquisitionStart in the parent Data3D Structure.
      double *timeStamp = nullptr;

      /// @brief Value = 0 if the timeStamp is considered valid, 1 otherwise
      int8_t *isTimeStampInvalid = nullptr;

      /// @name Extension: E57_EXT_surface_normals
      /// The following fields are part of the
      /// [E57_EXT_surface_normals](http://www.libe57.org/E57_EXT_surface_normals.txt) extension.
      ///@{

      /// @brief The X component of a surface normal vector.
      float *normalX = nullptr;
      /// @brief The Y component of a surface normal vector.
      float *normalY = nullptr;
      /// @brief The Z component of a surface normal vector.
      float *normalZ = nullptr;

      ///@}

   private:
      /// @brief Keeps track of whether we used the Data3D constructor or not so we can free our
      /// memory.
      bool _selfAllocated = false;
   };

   using Data3DPointsFloat = Data3DPointsData_t<float>;
   using Data3DPointsDouble = Data3DPointsData_t<double>;

   /// @deprecated Will be removed in 4.0. Use e57::Data3DPointsFloat.
   using Data3DPointsData [[deprecated( "Will be removed in 4.0. Use Data3DPointsFloat." )]] =
      Data3DPointsData_t<float>;
   /// @deprecated Will be removed in 4.0. Use e57::Data3DPointsDouble.
   using Data3DPointsData_d [[deprecated( "Will be removed in 4.0. Use Data3DPointsDouble." )]] =
      Data3DPointsData_t<double>;

   extern template struct Data3DPointsData_t<float>;
   extern template struct Data3DPointsData_t<double>;

   /// @brief Stores an image that is to be used only as a visual reference.
   struct E57_DLL VisualReferenceRepresentation
   {
      /// Size of JPEG format image data in BlobNode.
      int64_t jpegImageSize = 0;

      /// Size of PNG format image data in BlobNode.
      int64_t pngImageSize = 0;

      /// Size of PNG format image mask in BlobNode.
      int64_t imageMaskSize = 0;

      /// The image width (in pixels). Shall be positive.
      int32_t imageWidth = 0;

      /// The image height (in pixels). Shall be positive.
      int32_t imageHeight = 0;

      bool operator==( const VisualReferenceRepresentation &rhs ) const
      {
         return ( jpegImageSize == rhs.jpegImageSize ) && ( pngImageSize == rhs.pngImageSize ) &&
                ( imageMaskSize == rhs.imageMaskSize ) && ( imageWidth == rhs.imageWidth ) &&
                ( imageHeight == rhs.imageHeight );
      }

      bool operator!=( const VisualReferenceRepresentation &rhs ) const
      {
         return !operator==( rhs );
      }
   };

   /// @brief Stores an image that is mapped from 3D using the pinhole camera projection model.
   struct E57_DLL PinholeRepresentation
   {
      /// Size of JPEG format image data in BlobNode.
      int64_t jpegImageSize = 0;

      /// Size of PNG format image data in BlobNode.
      int64_t pngImageSize = 0;

      /// Size of PNG format image mask in BlobNode.
      int64_t imageMaskSize = 0;

      /// The image width (in pixels). Shall be positive.
      int32_t imageWidth = 0;
      /// The image height (in pixels). Shall be positive.
      int32_t imageHeight = 0;

      /// The camera's focal length (in meters). Shall be positive.
      double focalLength = 0.0;

      /// The width of the pixels in the camera (in meters). Shall be positive.
      double pixelWidth = 0.0;
      /// The height of the pixels in the camera (in meters). Shall be positive.
      double pixelHeight = 0.0;

      /// @brief The X coordinate in the image of the principal point, (in pixels).
      /// @details The principal point is the intersection of the z axis of the camera coordinate
      /// frame with the image plane.
      double principalPointX = 0.0;
      /// The Y coordinate in the image of the principal point (in pixels).
      double principalPointY = 0.0;

      bool operator==( const PinholeRepresentation &rhs ) const
      {
         return ( jpegImageSize == rhs.jpegImageSize ) && ( pngImageSize == rhs.pngImageSize ) &&
                ( imageMaskSize == rhs.imageMaskSize ) && ( imageWidth == rhs.imageWidth ) &&
                ( imageHeight == rhs.imageHeight ) && ( focalLength == rhs.focalLength ) &&
                ( pixelWidth == rhs.pixelWidth ) && ( pixelHeight == rhs.pixelHeight ) &&
                ( principalPointX == rhs.principalPointX ) &&
                ( principalPointY == rhs.principalPointY );
      }

      bool operator!=( const PinholeRepresentation &rhs ) const
      {
         return !operator==( rhs );
      }
   };

   /// @brief Stores an image that is mapped from 3D using a spherical projection model
   struct E57_DLL SphericalRepresentation
   {
      /// Size of JPEG format image data in BlobNode.
      int64_t jpegImageSize = 0;

      /// Size of PNG format image data in BlobNode.
      int64_t pngImageSize = 0;

      /// Size of PNG format image mask in BlobNode.
      int64_t imageMaskSize = 0;

      /// The image width (in pixels). Shall be positive
      int32_t imageWidth = 0;
      /// The image height (in pixels). Shall be positive
      int32_t imageHeight = 0;

      /// The width of a pixel in the image (in radians). Shall be positive
      double pixelWidth = 0.0;
      /// The height of a pixel in the image (in radians). Shall be positive.
      double pixelHeight = 0.0;

      bool operator==( const SphericalRepresentation &rhs ) const
      {
         return ( jpegImageSize == rhs.jpegImageSize ) && ( pngImageSize == rhs.pngImageSize ) &&
                ( imageMaskSize == rhs.imageMaskSize ) && ( imageWidth == rhs.imageWidth ) &&
                ( imageHeight == rhs.imageHeight ) && ( pixelWidth == rhs.pixelWidth ) &&
                ( pixelHeight == rhs.pixelHeight );
      }

      bool operator!=( const SphericalRepresentation &rhs ) const
      {
         return !operator==( rhs );
      }
   };

   /// @brief Stores an image that is mapped from 3D using a cylindrical projection model.
   struct E57_DLL CylindricalRepresentation
   {
      /// Size of JPEG format image data in Blob.
      int64_t jpegImageSize = 0;

      /// Size of PNG format image data in Blob.
      int64_t pngImageSize = 0;

      /// Size of PNG format image mask in Blob.
      int64_t imageMaskSize = 0;

      /// The image width (in pixels). Shall be positive
      int32_t imageWidth = 0;
      /// The image height (in pixels). Shall be positive
      int32_t imageHeight = 0;

      /// The width of a pixel in the image (in radians). Shall be positive.
      double pixelWidth = 0.0;
      /// The height of a pixel in the image (in meters). Shall be positive.
      double pixelHeight = 0.0;

      /// @brief The closest distance from the cylindrical image surface to the center of projection
      /// (that is, the radius of the cylinder) (in meters).
      /// @details Shall be non-negative.
      double radius = 0.0;

      /// @brief The Y coordinate in the image of the principal point (in pixels).
      /// @details This is the intersection of the z = 0 plane with the image.
      double principalPointY = 0.0;

      bool operator==( const CylindricalRepresentation &rhs ) const
      {
         return ( jpegImageSize == rhs.jpegImageSize ) && ( pngImageSize == rhs.pngImageSize ) &&
                ( imageMaskSize == rhs.imageMaskSize ) && ( imageWidth == rhs.imageWidth ) &&
                ( imageHeight == rhs.imageHeight ) && ( pixelWidth == rhs.pixelWidth ) &&
                ( pixelHeight == rhs.pixelHeight ) && ( radius == rhs.radius ) &&
                ( principalPointY == rhs.principalPointY );
      }

      bool operator!=( const CylindricalRepresentation &rhs ) const
      {
         return !operator==( rhs );
      }
   };

   /// @brief Stores an image from a camera
   struct E57_DLL Image2D
   {
      /// A user-defined name for the Image2D.
      ustring name;

      /// A globally unique identification string for the current version of the Image2D object
      ustring guid;

      /// A user-defined description of the Image2D
      ustring description;

      /// The date and time that the image was taken
      DateTime acquisitionDateTime;

      /// The globally unique identification string (guid element) for the Data3D that was being
      /// acquired when the picture was taken
      ustring associatedData3DGuid;

      /// The name of the manufacturer for the sensor used to collect the points in this Data3D.
      ustring sensorVendor;
      /// The model name or number for the sensor.
      ustring sensorModel;
      /// The serial number for the sensor.
      ustring sensorSerialNumber;

      /// A rigid body transform that describes the coordinate frame of the camera in the file-level
      /// coordinate system
      RigidBodyTransform pose;

      /// Representation for an image that does not define any camera projection model.
      /// The image is to be used for visual reference only
      VisualReferenceRepresentation visualReferenceRepresentation;

      /// Representation for an image using the pinhole camera projection model.
      PinholeRepresentation pinholeRepresentation;

      /// Representation for an image using the spherical camera projection model.
      SphericalRepresentation sphericalRepresentation;

      /// Representation for an image using the cylindrical camera projection model.
      CylindricalRepresentation cylindricalRepresentation;
   };

   /// @brief Identifies the format representation for the image data
   enum Image2DType
   {
      ImageNone = 0,    ///< No image data
      ImageJPEG = 1,    ///< JPEG format image data.
      ImagePNG = 2,     ///< PNG format image data.
      ImageMaskPNG = 3, ///< PNG format image mask.

      /// @deprecated Will be removed in 4.0. Use e57::ImageNone.
      E57_NO_IMAGE E57_DEPRECATED_ENUM( "Will be removed in 4.0. Use ImageNone." ) = ImageNone,
      /// @deprecated Will be removed in 4.0. Use e57::ImageJPEG.
      E57_JPEG_IMAGE E57_DEPRECATED_ENUM( "Will be removed in 4.0. Use ImageJPEG." ) = ImageJPEG,
      /// @deprecated Will be removed in 4.0. Use e57::ImagePNG.
      E57_PNG_IMAGE E57_DEPRECATED_ENUM( "Will be removed in 4.0. Use ImagePNG." ) = ImagePNG,
      /// @deprecated Will be removed in 4.0. Use e57::ImageMaskPNG.
      E57_PNG_IMAGE_MASK E57_DEPRECATED_ENUM( "Will be removed in 4.0. Use ImageMaskPNG." ) =
         ImageMaskPNG,
   };

   /// @brief Identifies the representation for the image data
   enum Image2DProjection
   {
      ProjectionNone = 0,        ///< No representation for the image data is present
      ProjectionVisual = 1,      ///< VisualReferenceRepresentation for the image data
      ProjectionPinhole = 2,     ///< PinholeRepresentation for the image data
      ProjectionSpherical = 3,   ///< SphericalRepresentation for the image data
      ProjectionCylindrical = 4, ///< CylindricalRepresentation for the image data

      /// @deprecated Will be removed in 4.0. Use e57::ProjectionNone.
      E57_NO_PROJECTION E57_DEPRECATED_ENUM( "Will be removed in 4.0. Use ProjectionNone." ) =
         ProjectionNone,
      /// @deprecated Will be removed in 4.0. Use e57::ProjectionVisual.
      E57_VISUAL E57_DEPRECATED_ENUM( "Will be removed in 4.0. Use ProjectionVisual." ) =
         ProjectionVisual,
      /// @deprecated Will be removed in 4.0. Use e57::ProjectionPinhole.
      E57_PINHOLE E57_DEPRECATED_ENUM( "Will be removed in 4.0. Use ProjectionPinhole." ) =
         ProjectionPinhole,
      /// @deprecated Will be removed in 4.0. Use e57::ProjectionSpherical.
      E57_SPHERICAL E57_DEPRECATED_ENUM( "Will be removed in 4.0. Use ProjectionSpherical." ) =
         ProjectionSpherical,
      /// @deprecated Will be removed in 4.0. Use e57::ProjectionCylindrical.
      E57_CYLINDRICAL E57_DEPRECATED_ENUM( "Will be removed in 4.0. Use ProjectionCylindrical." ) =
         ProjectionCylindrical,
   };
} // end namespace e57
