// Copyright (c) 2017 GeometryFactory // // This file is part of CGAL (www.cgal.org); // // $URL$ // $Id$ // SPDX-License-Identifier: LGPL-3.0-or-later OR LicenseRef-Commercial // // Author(s) : Simon Giraudot #ifndef CGAL_IO_PLY_PLY_READER_H #define CGAL_IO_PLY_PLY_READER_H #include #include #include #include #include #include #include #include #include #include #include #include #include #define TRY_TO_GENERATE_PROPERTY(STD_TYPE, T_TYPE, TYPE) \ if(type == STD_TYPE || type == T_TYPE) \ m_elements.back().add_property(new PLY_read_typed_number< TYPE >(name, format)) #define TRY_TO_GENERATE_SIZED_LIST_PROPERTY(STD_SIZE_TYPE, T_SIZE_TYPE, SIZE_TYPE, STD_INDEX_TYPE, T_INDEX_TYPE, INDEX_TYPE) \ if((size_type == STD_SIZE_TYPE || size_type == T_SIZE_TYPE) && \ (index_type == STD_INDEX_TYPE || index_type == T_INDEX_TYPE)) \ m_elements.back().add_property(new PLY_read_typed_list_with_typed_size< SIZE_TYPE , INDEX_TYPE >(name, format)) #define TRY_TO_GENERATE_LIST_PROPERTY(STD_INDEX_TYPE, T_INDEX_TYPE, INDEX_TYPE) \ TRY_TO_GENERATE_SIZED_LIST_PROPERTY("uchar", "uint8", boost::uint8_t, STD_INDEX_TYPE, T_INDEX_TYPE, INDEX_TYPE); \ else TRY_TO_GENERATE_SIZED_LIST_PROPERTY("char", "int8", boost::int8_t, STD_INDEX_TYPE, T_INDEX_TYPE, INDEX_TYPE); \ else TRY_TO_GENERATE_SIZED_LIST_PROPERTY("ushort", "uint16", boost::uint16_t, STD_INDEX_TYPE, T_INDEX_TYPE, INDEX_TYPE); \ else TRY_TO_GENERATE_SIZED_LIST_PROPERTY("short", "int16", boost::int16_t, STD_INDEX_TYPE, T_INDEX_TYPE, INDEX_TYPE); \ else TRY_TO_GENERATE_SIZED_LIST_PROPERTY("uint", "uint32", boost::uint32_t, STD_INDEX_TYPE, T_INDEX_TYPE, INDEX_TYPE); \ else TRY_TO_GENERATE_SIZED_LIST_PROPERTY("int", "int32", boost::int32_t, STD_INDEX_TYPE, T_INDEX_TYPE, INDEX_TYPE) namespace CGAL { namespace IO { /// \cond SKIP_IN_MANUAL // PLY types: // name type number of bytes // --------------------------------------- // char character 1 // uchar unsigned character 1 // short short integer 2 // ushort unsigned short integer 2 // int integer 4 // uint unsigned integer 4 // float single-precision float 4 // double double-precision float 8 template struct PLY_property { typedef T type; const char* name; PLY_property(const char* name) : name(name) { } }; // Use a double property for all kernels... template struct Convert_FT { typedef double type; }; // ...except if kernel uses type float template <> struct Convert_FT { typedef float type; }; template struct Get_FT_from_map { typedef typename Convert_FT::value_type>::Kernel::FT>::type type; }; template std::tuple::Kernel::Construct_point_3, PLY_property::type>, PLY_property::type>, PLY_property::type> > make_ply_point_reader(PointMap point_map) { return std::make_tuple(point_map, typename Kernel_traits::Kernel::Construct_point_3(), PLY_property::type>("x"), PLY_property::type>("y"), PLY_property::type>("z")); } template std::tuple::Kernel::Construct_vector_3, PLY_property::type>, PLY_property::type>, PLY_property::type> > make_ply_normal_reader(VectorMap normal_map) { return std::make_tuple(normal_map, typename Kernel_traits::Kernel::Construct_vector_3(), PLY_property::type>("nx"), PLY_property::type>("ny"), PLY_property::type>("nz")); } template std::tuple::type>, PLY_property::type>, PLY_property::type> > make_ply_point_writer(PointMap point_map) { return std::make_tuple(point_map, PLY_property::type>("x"), PLY_property::type>("y"), PLY_property::type>("z")); } template std::tuple::type>, PLY_property::type>, PLY_property::type> > make_ply_normal_writer(VectorMap normal_map) { return std::make_tuple(normal_map, PLY_property::type>("nx"), PLY_property::type>("ny"), PLY_property::type>("nz")); } namespace internal { class PLY_read_number { protected: std::string m_name; std::size_t m_format; public: PLY_read_number(std::string name, std::size_t format) : m_name(name), m_format(format) { } virtual ~PLY_read_number() { } const std::string& name() const { return m_name; } virtual void get(std::istream& stream) const = 0; // The two following functions prevent the stream to only extract // ONE character (= what the types char imply) by requiring // explicitely an integer object when reading the stream void read_ascii(std::istream& stream, char& c) const { short s; if(stream >> s) c = static_cast(s); else { c = 0; stream.clear(std::ios::badbit); } } void read_ascii(std::istream& stream, signed char& c) const { short s; if(stream >> s) c = static_cast(s); else { c = 0; stream.clear(std::ios::badbit); } } void read_ascii(std::istream& stream, unsigned char& c) const { unsigned short s; if(stream >> s) c = static_cast(s); else { c = 0; stream.clear(std::ios::badbit); } } void read_ascii(std::istream& stream, float& t) const { if(!(stream >> IO::iformat(t))) stream.clear(std::ios::badbit); } void read_ascii(std::istream& stream, double& t) const { if(!(stream >> iformat(t))) stream.clear(std::ios::badbit); } // Default template when Type is not a char type template void read_ascii(std::istream& stream, Type& t) const { if(!(stream >> t)) stream.clear(std::ios::badbit); } template Type read(std::istream& stream) const { if(m_format == 0) // ASCII { Type t; read_ascii(stream, t); return t; } else // Binary (2 = little endian) { union { char uChar[sizeof(Type)]; Type type; } buffer; std::size_t size = sizeof(Type); stream.read(buffer.uChar, size); if(m_format == 2) // Big endian { for(std::size_t i = 0; i < size / 2; ++ i) { unsigned char tmp = buffer.uChar[i]; buffer.uChar[i] = buffer.uChar[size - 1 - i]; buffer.uChar[size - 1 - i] = tmp; } } return buffer.type; } return Type(); } }; template class PLY_read_typed_number : public PLY_read_number { mutable Type m_buffer; public: PLY_read_typed_number(std::string name, std::size_t format) : PLY_read_number(name, format) { } void get(std::istream& stream) const { m_buffer =(this->read(stream)); } const Type& buffer() const { return m_buffer; } }; template class PLY_read_typed_list : public PLY_read_number { protected: mutable std::vector m_buffer; public: PLY_read_typed_list(std::string name, std::size_t format) : PLY_read_number(name, format) { } virtual void get(std::istream& stream) const = 0; const std::vector& buffer() const { return m_buffer; } }; template class PLY_read_typed_list_with_typed_size : public PLY_read_typed_list { public: PLY_read_typed_list_with_typed_size(std::string name, std::size_t format) : PLY_read_typed_list(name, format) { } void get(std::istream& stream) const { std::size_t size = static_cast(this->template read(stream)); this->m_buffer.resize(size); for(std::size_t i = 0; i < size; ++ i) this->m_buffer[i] = this->template read(stream); } }; class PLY_element { std::string m_name; std::size_t m_number; std::vector m_properties; public: PLY_element(const std::string& name, std::size_t number) : m_name(name), m_number(number) { } PLY_element(const PLY_element& other) : m_name(other.m_name), m_number(other.m_number), m_properties(other.m_properties) { const_cast(other).m_properties.clear(); } PLY_element& operator=(const PLY_element& other) { m_name = other.m_name; m_number = other.m_number; m_properties = other.m_properties; const_cast(other).m_properties.clear(); return *this; } ~PLY_element() { for(std::size_t i = 0; i < m_properties.size(); ++ i) delete m_properties[i]; } const std::string& name() const { return m_name; } std::size_t number_of_items() const { return m_number; } std::size_t number_of_properties() const { return m_properties.size(); } PLY_read_number* property(std::size_t idx) { return m_properties[idx]; } void add_property(PLY_read_number* read_number) { m_properties.push_back(read_number); } template bool has_property(const char* tag) { return has_property(tag, Type()); } template bool has_property(const char* tag, const std::vector&) { for(std::size_t i = 0; i < number_of_properties(); ++ i) if(m_properties[i]->name() == tag) return (dynamic_cast*>(m_properties[i]) != nullptr); return false; } template bool has_property(const char* tag, Type) { for(std::size_t i = 0; i < number_of_properties(); ++ i) if(m_properties[i]->name() == tag) return (dynamic_cast*>(m_properties[i]) != nullptr); return false; } bool has_property(const char* tag, double) { for(std::size_t i = 0; i < number_of_properties(); ++ i) if(m_properties[i]->name() == tag) return (dynamic_cast*>(m_properties[i]) != nullptr || dynamic_cast*>(m_properties[i]) != nullptr); return false; } template void assign(Type& t, const char* tag) { for(std::size_t i = 0; i < number_of_properties(); ++ i) if(m_properties[i]->name() == tag) { PLY_read_typed_number* property = dynamic_cast*>(m_properties[i]); CGAL_assertion(property != nullptr); t = property->buffer(); return; } t = {}; } template void assign(std::vector& t, const char* tag) { for(std::size_t i = 0; i < number_of_properties(); ++ i) if(m_properties[i]->name() == tag) { PLY_read_typed_list* property = dynamic_cast*>(m_properties[i]); CGAL_assertion(property != nullptr); t = property->buffer(); return; } t = {}; } void assign(double& t, const char* tag) { for(std::size_t i = 0; i < number_of_properties(); ++ i) if(m_properties[i]->name() == tag) { PLY_read_typed_number* property_double = dynamic_cast*>(m_properties[i]); if(property_double == nullptr) { PLY_read_typed_number* property_float = dynamic_cast*>(m_properties[i]); CGAL_assertion(property_float != nullptr); t = property_float->buffer(); } else t = property_double->buffer(); return; } t = {}; } }; class PLY_reader { std::vector m_elements; std::string m_comments; bool m_verbose; public: PLY_reader(bool verbose) : m_verbose(verbose) { } std::size_t number_of_elements() const { return m_elements.size(); } PLY_element& element(std::size_t idx) { return m_elements[idx]; } const std::string& comments() const { return m_comments; } template bool init(Stream& stream) { std::size_t lineNumber = 0; // current line number enum Format { ASCII = 0, BINARY_LITTLE_ENDIAN = 1, BINARY_BIG_ENDIAN = 2}; Format format = ASCII; std::string line; std::istringstream iss; while(getline(stream,line)) { iss.clear(); iss.str(line); ++ lineNumber; // Reads file signature on first line if(lineNumber == 1) { std::string signature; if(!(iss >> signature) || (signature != "ply")) { // if wrong file format if(m_verbose) std::cerr << "Error: incorrect file format line " << lineNumber << " of file" << std::endl; return false; } } // Reads format on 2nd line else if(lineNumber == 2) { std::string tag, format_string, version; if( !(iss >> tag >> format_string >> version) ) { if(m_verbose) std::cerr << "Error line " << lineNumber << " of file" << std::endl; return false; } if(format_string == "ascii") format = ASCII; else if(format_string == "binary_little_endian") format = BINARY_LITTLE_ENDIAN; else if(format_string == "binary_big_endian") format = BINARY_BIG_ENDIAN; else { if(m_verbose) std::cerr << "Error: unknown file format \"" << format_string << "\" line " << lineNumber << std::endl; return false; } } // Comments and vertex properties else { std::string keyword; if(!(iss >> keyword)) { if(m_verbose) std::cerr << "Error line " << lineNumber << " of file" << std::endl; return false; } if(keyword == "property") { std::string type, name; if(!(iss >> type >> name)) { if(m_verbose) std::cerr << "Error line " << lineNumber << " of file" << std::endl; return false; } if(type == "list") // Special case { std::string size_type = name; std::string index_type; name.clear(); if(!(iss >> index_type >> name)) { if(m_verbose) std::cerr << "Error line " << lineNumber << " of file" << std::endl; return false; } TRY_TO_GENERATE_LIST_PROPERTY("char", "int8", boost::int8_t); else TRY_TO_GENERATE_LIST_PROPERTY("uchar", "uint8", boost::uint8_t); else TRY_TO_GENERATE_LIST_PROPERTY("short", "int16", boost::int16_t); else TRY_TO_GENERATE_LIST_PROPERTY("ushort", "uint16", boost::uint16_t); else TRY_TO_GENERATE_LIST_PROPERTY("int", "int32", boost::int32_t); else TRY_TO_GENERATE_LIST_PROPERTY("uint", "uint32", boost::uint32_t); else TRY_TO_GENERATE_LIST_PROPERTY("float", "float32", float); else TRY_TO_GENERATE_LIST_PROPERTY("double", "float64", double); } else { TRY_TO_GENERATE_PROPERTY("char", "int8", boost::int8_t); else TRY_TO_GENERATE_PROPERTY("uchar", "uint8", boost::uint8_t); else TRY_TO_GENERATE_PROPERTY("short", "int16", boost::int16_t); else TRY_TO_GENERATE_PROPERTY("ushort", "uint16", boost::uint16_t); else TRY_TO_GENERATE_PROPERTY("int", "int32", boost::int32_t); else TRY_TO_GENERATE_PROPERTY("uint", "uint32", boost::uint32_t); else TRY_TO_GENERATE_PROPERTY("float", "float32", float); else TRY_TO_GENERATE_PROPERTY("double", "float64", double); } continue; } else if(keyword == "comment") { std::string str = iss.str(); if(str.size() > 8) { std::copy(str.begin() + 8, str.end(), std::back_inserter(m_comments)); m_comments += "\n"; } } else if(keyword == "element") { std::string type; std::size_t number; if(!(iss >> type >> number)) { if(m_verbose) std::cerr << "Error line " << lineNumber << " of file" << std::endl; return false; } m_elements.push_back(PLY_element(type, number)); } // When end_header is reached, stop loop and begin reading points else if(keyword == "end_header") break; } } return true; } ~PLY_reader() { } }; template void get_value(Reader& r, T& v, PLY_property& wrapper) { return r.assign(v, wrapper.name); } template struct Filler { template static void fill(Reader& r, Value_tuple& values, PLY_property_tuple wrappers) { get_value(r, std::get(values), std::get(wrappers)); Filler::fill(r, values, wrappers); } }; template struct seq { }; template struct gens : gens { }; template struct gens<0, S...> { typedef seq type; }; template ValueType call_functor(Functor f, Tuple t, seq) { return f(std::get(t) ...); } template ValueType call_functor(Functor f, std::tuple& t) { return call_functor(f, t, typename gens::type()); } template<> struct Filler<0> { template static void fill(Reader& r, Value_tuple& values, PLY_property_tuple wrappers) { get_value(r, std::get<0>(values), std::get<2>(wrappers)); } }; template void process_properties(PLY_element& element, OutputValueType& new_element, std::tuple...>&& current) { typedef typename boost::property_traits::value_type PmapValueType; std::tuple values; Filler::fill(element, values, current); PmapValueType new_value = call_functor(std::get<1>(current), values); put(std::get<0>(current), new_element, new_value); } template void process_properties(PLY_element& element, OutputValueType& new_element, std::tuple...>&& current, NextPropertyBinder&& next, PropertyMapBinders&& ... properties) { typedef typename boost::property_traits::value_type PmapValueType; std::tuple values; Filler::fill(element, values, current); PmapValueType new_value = call_functor(std::get<1>(current), values); put(std::get<0>(current), new_element, new_value); process_properties(element, new_element, std::forward(next), std::forward(properties)...); } template void process_properties(PLY_element& element, OutputValueType& new_element, std::pair >&& current) { T new_value = T(); element.assign(new_value, current.second.name); put(current.first, new_element, new_value); } template void process_properties(PLY_element& element, OutputValueType& new_element, std::pair >&& current, NextPropertyBinder&& next, PropertyMapBinders&& ... properties) { T new_value = T(); element.assign(new_value, current.second.name); put(current.first, new_element, new_value); process_properties(element, new_element, std::forward(next), std::forward(properties)...); } template bool read_PLY_faces(std::istream& in, PLY_element& element, PolygonRange& polygons, ColorOutputIterator fc_out, const char* vertex_indices_tag, std::enable_if_t::value>* = nullptr) { typedef CGAL::IO::Color Color_rgb; bool has_colors = false; std::string rtag = "r", gtag = "g", btag = "b"; if((element.has_property("red") || element.has_property("r")) && (element.has_property("green") || element.has_property("g")) && (element.has_property("blue") || element.has_property("b"))) { has_colors = true; if(element.has_property("red")) { rtag = "red"; gtag = "green"; btag = "blue"; } } for(std::size_t j = 0; j < element.number_of_items(); ++ j) { for(std::size_t k = 0; k < element.number_of_properties(); ++ k) { PLY_read_number* property = element.property(k); property->get(in); if(in.fail()) return false; } std::tuple, boost::uint8_t, boost::uint8_t, boost::uint8_t> new_face; if(has_colors) { process_properties(element, new_face, std::make_pair(CGAL::make_nth_of_tuple_property_map<0>(new_face), PLY_property >(vertex_indices_tag)), std::make_pair(CGAL::make_nth_of_tuple_property_map<1>(new_face), PLY_property(rtag.c_str())), std::make_pair(CGAL::make_nth_of_tuple_property_map<2>(new_face), PLY_property(gtag.c_str())), std::make_pair(CGAL::make_nth_of_tuple_property_map<3>(new_face), PLY_property(btag.c_str()))); *fc_out++ = Color_rgb(get<1>(new_face), get<2>(new_face), get<3>(new_face)); } else { process_properties(element, new_face, std::make_pair(CGAL::make_nth_of_tuple_property_map<0>(new_face), PLY_property >(vertex_indices_tag))); } polygons.emplace_back(); ::CGAL::internal::resize(polygons.back(), get<0>(new_face).size()); for(std::size_t i = 0; i < get<0>(new_face).size(); ++ i) polygons.back()[i] = std::size_t(get<0>(new_face)[i]); } return true; } template bool read_PLY_faces(std::istream& in, PLY_element& element, PolygonRange& polygons, ColorRange& fcolors, const char* vertex_indices_tag, std::enable_if_t< boost::has_range_const_iterator::value >* = nullptr) { return read_PLY_faces(in, element, polygons, std::back_inserter(fcolors), vertex_indices_tag); } } // namespace PLY } // namespace internal #ifndef CGAL_NO_DEPREACTED_CODE using IO::PLY_property; using IO::make_ply_normal_reader; using IO::make_ply_normal_writer; using IO::make_ply_point_reader; using IO::make_ply_point_writer; #endif /// \endcond } // namespace CGAL #endif // CGAL_IO_PLY_PLY_READER_H