diff --git a/UVAtlasTool/Mesh.h b/UVAtlasTool/Mesh.h index 4b437e49..804ee64a 100644 --- a/UVAtlasTool/Mesh.h +++ b/UVAtlasTool/Mesh.h @@ -147,6 +147,7 @@ class Mesh }; HRESULT ExportToOBJ(const wchar_t* szFileName, _In_ size_t nMaterials, _In_reads_opt_(nMaterials) const Material* materials) const; + HRESULT ExportToPLY(const wchar_t* szFileName/*, _In_ size_t nMaterials, _In_reads_opt_(nMaterials) const Material* materials*/) const; HRESULT ExportToVBO(_In_z_ const wchar_t* szFileName) const noexcept; HRESULT ExportToCMO(_In_z_ const wchar_t* szFileName, _In_ size_t nMaterials, _In_reads_opt_(nMaterials) const Material* materials) const noexcept; HRESULT ExportToSDKMESH(_In_z_ const wchar_t* szFileName, @@ -179,4 +180,5 @@ class Mesh std::wstring mtlFileName; void ExportToOBJ(std::wostream& os, _In_ size_t nMaterials, _In_reads_opt_(nMaterials) const Material* materials) const; + }; diff --git a/UVAtlasTool/UVAtlas.cpp b/UVAtlasTool/UVAtlas.cpp index ca9eb8ac..c919e5fe 100644 --- a/UVAtlasTool/UVAtlas.cpp +++ b/UVAtlasTool/UVAtlas.cpp @@ -87,6 +87,7 @@ namespace OPT_CMO, OPT_VBO, OPT_WAVEFRONT_OBJ, + OPT_PLY, OPT_CLOCKWISE, OPT_FORCE_32BIT_IB, OPT_OVERWRITE, @@ -172,6 +173,7 @@ namespace { L"cmo", OPT_CMO }, { L"vbo", OPT_VBO }, { L"wf", OPT_WAVEFRONT_OBJ }, + { L"ply", OPT_PLY}, { L"cw", OPT_CLOCKWISE }, { L"ib32", OPT_FORCE_32BIT_IB }, { L"y", OPT_OVERWRITE }, @@ -224,6 +226,10 @@ HRESULT LoadFromOBJ(const wchar_t* szFilename, std::unique_ptr& inMesh, std::vector& inMaterial, bool ccw, bool dds); + +HRESULT LoadFromPLY(const wchar_t* szFilename, + std::unique_ptr& inMesh, const bool preload_into_memory = false); + ////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// @@ -879,6 +885,14 @@ int __cdecl wmain(_In_ int argc, _In_z_count_(argc) wchar_t* argv[]) } break; + case OPT_PLY: + if (dwOptions & (uint64_t(1) << OPT_SECOND_UV)) + { + wprintf(L"-uv2 is not supported by PLY\n"); + return 1; + } + break; + case OPT_SECOND_UV: if (dwOptions & ((uint64_t(1) << OPT_VBO) | (uint64_t(1) << OPT_CMO) | (uint64_t(1) << OPT_WAVEFRONT_OBJ))) { @@ -1008,6 +1022,11 @@ int __cdecl wmain(_In_ int argc, _In_z_count_(argc) wchar_t* argv[]) wprintf(L"\nERROR: Autodesk FBX files not supported\n"); return 1; } + else if (_wcsicmp(ext, L".ply") == 0) + { + LoadFromPLY(pConv->szSrc, inMesh); + hr = S_OK; + } else { hr = LoadFromOBJ(pConv->szSrc, inMesh, inMaterial, @@ -1072,7 +1091,7 @@ int __cdecl wmain(_In_ int argc, _In_z_count_(argc) wchar_t* argv[]) // Adjacency float epsilon = (dwOptions & (uint64_t(1) << OPT_GEOMETRIC_ADJ)) ? 1e-5f : 0.f; - hr = inMesh->GenerateAdjacency(epsilon); + hr = inMesh->GenerateAdjacency(epsilon); // here binary ply doesnt work ..? if (FAILED(hr)) { wprintf(L"\nERROR: Failed generating adjacency (%08X%ls)\n", @@ -1099,6 +1118,8 @@ int __cdecl wmain(_In_ int argc, _In_z_count_(argc) wchar_t* argv[]) } else { + + size_t nNewVerts = inMesh->GetVertexCount(); if (nVerts != nNewVerts) { @@ -1495,6 +1516,10 @@ int __cdecl wmain(_In_ int argc, _In_z_count_(argc) wchar_t* argv[]) { wcscpy_s(outputExt, L".obj"); } + else if (dwOptions & (uint64_t(1) << OPT_PLY)) + { + wcscpy_s(outputExt, L".ply"); + } else { wcscpy_s(outputExt, L".sdkmesh"); @@ -1567,6 +1592,10 @@ int __cdecl wmain(_In_ int argc, _In_z_count_(argc) wchar_t* argv[]) { hr = inMesh->ExportToOBJ(outputPath, inMaterial.size(), inMaterial.empty() ? nullptr : inMaterial.data()); } + else if (!_wcsicmp(outputExt, L".ply")) + { + hr = inMesh->ExportToPLY(outputPath); + } else if (!_wcsicmp(outputExt, L".x")) { wprintf(L"\nERROR: Legacy Microsoft X files not supported\n"); diff --git a/UVAtlasTool/UVAtlasTool_2019.vcxproj b/UVAtlasTool/UVAtlasTool_2019.vcxproj index 2b4a1bce..72ee50d3 100644 --- a/UVAtlasTool/UVAtlasTool_2019.vcxproj +++ b/UVAtlasTool/UVAtlasTool_2019.vcxproj @@ -332,6 +332,7 @@ + diff --git a/UVAtlasTool/UVAtlasTool_2019.vcxproj.filters b/UVAtlasTool/UVAtlasTool_2019.vcxproj.filters index 771a84a2..b642432a 100644 --- a/UVAtlasTool/UVAtlasTool_2019.vcxproj.filters +++ b/UVAtlasTool/UVAtlasTool_2019.vcxproj.filters @@ -8,6 +8,9 @@ {c8076e0b-beef-46c6-8c4b-128ccc82a987} + + {337c9091-8663-40bd-8058-df2e01b62f79} + @@ -16,6 +19,9 @@ Wavefront OBJ + + Ply + diff --git a/UVAtlasTool/tinyply.cpp b/UVAtlasTool/tinyply.cpp new file mode 100644 index 00000000..bd18b5fa --- /dev/null +++ b/UVAtlasTool/tinyply.cpp @@ -0,0 +1,698 @@ +// This file exists to create a nice static or shared library via cmake +// but can otherwise be omitted if you prefer to compile tinyply +// directly into your own project. +#define TINYPLY_IMPLEMENTATION +#include "tinyply.h" +#include "Mesh.h" + + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + + + + + +using namespace DirectX; + +namespace +{ + + + namespace VBO + { + +#pragma pack(push,1) + + struct header_t + { + uint32_t numVertices; + uint32_t numIndices; + }; + + struct vertex_t + { + DirectX::XMFLOAT3 position; + DirectX::XMFLOAT3 normal; + DirectX::XMFLOAT2 textureCoordinate; + }; + +#pragma pack(pop) + + static_assert(sizeof(header_t) == 8, "VBO header size mismatch"); + static_assert(sizeof(vertex_t) == 32, "VBO vertex size mismatch"); + } // namespace + + + inline std::vector read_file_binary(const std::wstring& pathToFile) + { + std::ifstream file(pathToFile, std::ios::binary); + std::vector fileBufferBytes; + + if (file.is_open()) + { + file.seekg(0, std::ios::end); + size_t sizeBytes = file.tellg(); + file.seekg(0, std::ios::beg); + fileBufferBytes.resize(sizeBytes); + if (file.read((char*)fileBufferBytes.data(), sizeBytes)) return fileBufferBytes; + } + else throw std::runtime_error("could not open binary ifstream to path "); + return fileBufferBytes; + } + + struct memory_buffer : public std::streambuf + { + char* p_start{ nullptr }; + char* p_end{ nullptr }; + size_t size; + + memory_buffer(char const* first_elem, size_t size) + : p_start(const_cast(first_elem)), p_end(p_start + size), size(size) + { + setg(p_start, p_start, p_end); + } + + pos_type seekoff(off_type off, std::ios_base::seekdir dir, std::ios_base::openmode which) override + { + if (dir == std::ios_base::cur) gbump(static_cast(off)); + else setg(p_start, (dir == std::ios_base::beg ? p_start : p_end) + off, p_end); + return gptr() - p_start; + } + + pos_type seekpos(pos_type pos, std::ios_base::openmode which) override + { + return seekoff(pos, std::ios_base::beg, which); + } + }; + + struct memory_stream : virtual memory_buffer, public std::istream + { + memory_stream(char const* first_elem, size_t size) + : memory_buffer(first_elem, size), std::istream(static_cast(this)) {} + }; + + class manual_timer + { + std::chrono::high_resolution_clock::time_point t0; + double timestamp{ 0.0 }; + public: + void start() { t0 = std::chrono::high_resolution_clock::now(); } + void stop() { timestamp = std::chrono::duration(std::chrono::high_resolution_clock::now() - t0).count() * 1000.0; } + const double& get() { return timestamp; } + }; + + struct float2 { float x, y; }; + struct float3 { float x, y, z; }; + struct double3 { double x, y, z; }; + struct uint3 { uint32_t x, y, z; }; + struct uint4 { uint32_t x, y, z, w; }; + + struct geometry + { + std::vector vertices; + std::vector normals; + std::vector texcoords; + std::vector triangles; + }; + +#pragma pack(push,1) + + struct Vertex + { + DirectX::XMFLOAT3 position; + DirectX::XMFLOAT3 normal; + DirectX::XMFLOAT2 textureCoordinate; + }; + + +#pragma pack(pop) + + + + std::wstring ProcessTextureFileName(const wchar_t* inName, bool dds) + { + if (!inName || !*inName) + return std::wstring(); + + wchar_t txext[_MAX_EXT] = {}; + wchar_t txfname[_MAX_FNAME] = {}; + _wsplitpath_s(inName, nullptr, 0, nullptr, 0, txfname, _MAX_FNAME, txext, _MAX_EXT); + + if (dds) + { + wcscpy_s(txext, L".dds"); + } + + wchar_t texture[_MAX_PATH] = {}; + _wmakepath_s(texture, nullptr, nullptr, txfname, txext); + return std::wstring(texture); + } +} + +//-------------------------------------------------------------------------------------- +HRESULT LoadFromPLY( + const wchar_t* szFileName, + std::unique_ptr& inMesh, const bool preload_into_memory = false ) +{ + + const std::wstring& filepath = szFileName; + + std::cout << "........................................................................\n"; + std::wcout << "Now Reading: " << filepath << std::endl; + + std::unique_ptr file_stream; + std::vector byte_buffer; + + try + { + // For most files < 1gb, pre-loading the entire file upfront and wrapping it into a + // stream is a net win for parsing speed, about 40% faster. + if (preload_into_memory) + { + byte_buffer = read_file_binary(filepath); + file_stream.reset(new memory_stream((char*)byte_buffer.data(), byte_buffer.size())); + } + else + { + file_stream.reset(new std::ifstream(filepath, std::ios::binary)); + } + + if (!file_stream || file_stream->fail()) throw std::runtime_error("file_stream failed to open "); + + file_stream->seekg(0, std::ios::end); + const float size_mb = file_stream->tellg() * float(1e-6); + file_stream->seekg(0, std::ios::beg); + + PlyFile file; + file.parse_header(*file_stream); + + std::cout << "\t[ply_header] Type: " << (file.is_binary_file() ? "binary" : "ascii") << std::endl; + for (const auto& c : file.get_comments()) std::cout << "\t[ply_header] Comment: " << c << std::endl; + for (const auto& c : file.get_info()) std::cout << "\t[ply_header] Info: " << c << std::endl; + + for (const auto& e : file.get_elements()) + { + std::cout << "\t[ply_header] element: " << e.name << " (" << e.size << ")" << std::endl; + for (const auto& p : e.properties) + { + std::cout << "\t[ply_header] \tproperty: " << p.name << " (type=" << tinyply::PropertyTable[p.propertyType].str << ")"; + if (p.isList) std::cout << " (list_type=" << tinyply::PropertyTable[p.listType].str << ")"; + std::cout << std::endl; + } + } + + // Because most people have their own mesh types, tinyply treats parsed data as structured/typed byte buffers. + // See examples below on how to marry your own application-specific data structures with this one. + std::shared_ptr vertices, normals, colors, texcoords, faces, tripstrip; + + // The header information can be used to programmatically extract properties on elements + // known to exist in the header prior to reading the data. For brevity of this sample, properties + // like vertex position are hard-coded: + try { vertices = file.request_properties_from_element("vertex", { "x", "y", "z" }); } + catch (const std::exception& e) { std::cerr << "tinyply exception: " << e.what() << std::endl; } + + try { normals = file.request_properties_from_element("vertex", { "nx", "ny", "nz" }); } + catch (const std::exception& e) { std::cerr << "tinyply exception: " << e.what() << std::endl; } + + try { colors = file.request_properties_from_element("vertex", { "red", "green", "blue", "alpha" }); } + catch (const std::exception& e) { std::cerr << "tinyply exception: " << e.what() << std::endl; } + + try { colors = file.request_properties_from_element("vertex", { "r", "g", "b", "a" }); } + catch (const std::exception& e) { std::cerr << "tinyply exception: " << e.what() << std::endl; } + + try { texcoords = file.request_properties_from_element("vertex", { "u", "v" }); } + catch (const std::exception& e) { std::cerr << "tinyply exception: " << e.what() << std::endl; } + + // Providing a list size hint (the last argument) is a 2x performance improvement. If you have + // arbitrary ply files, it is best to leave this 0. + try { faces = file.request_properties_from_element("face", { "vertex_indices" }, 3); } + catch (const std::exception& e) { std::cerr << "tinyply exception: " << e.what() << std::endl; } + + // Tristrips must always be read with a 0 list size hint (unless you know exactly how many elements + // are specifically in the file, which is unlikely); + try { tripstrip = file.request_properties_from_element("tristrips", { "vertex_indices" }, 0); } + catch (const std::exception& e) { std::cerr << "tinyply exception: " << e.what() << std::endl; } + + manual_timer read_timer; + + read_timer.start(); + file.read(*file_stream); + read_timer.stop(); + + const float parsing_time = read_timer.get() / 1000.f; + std::cout << "\tparsing " << size_mb << "mb in " << parsing_time << " seconds [" << (size_mb / parsing_time) << " MBps]" << std::endl; + + if (vertices) std::cout << "\tRead " << vertices->count << " total vertices " << std::endl; + if (normals) std::cout << "\tRead " << normals->count << " total vertex normals " << std::endl; + if (colors) std::cout << "\tRead " << colors->count << " total vertex colors " << std::endl; + if (texcoords) std::cout << "\tRead " << texcoords->count << " total vertex texcoords " << std::endl; + if (faces) std::cout << "\tRead " << faces->count << " total faces (triangles) " << std::endl; + if (tripstrip) std::cout << "\tRead " << (tripstrip->buffer.size_bytes() / tinyply::PropertyTable[tripstrip->t].stride) << " total indicies (tristrip) " << std::endl; + + + inMesh.reset(new (std::nothrow) Mesh); + if (!inMesh) + return E_OUTOFMEMORY; + + std::unique_ptr pos(new (std::nothrow) XMFLOAT3[vertices->count]); + std::unique_ptr norm(new (std::nothrow) XMFLOAT3[vertices->count]); + std::unique_ptr texcoord(new (std::nothrow) XMFLOAT2[vertices->count]); + if (!pos || !norm || !texcoord) + return E_OUTOFMEMORY; + + std::vector< Vertex > verticeData; + + const size_t numVerticesBytesVert = vertices->buffer.size_bytes(); + std::vector verts(vertices->count); + std::memcpy(verts.data(), vertices->buffer.get(), numVerticesBytesVert); + + std::vector norms; + if (normals) { + const size_t numVerticesBytesNorm = normals->buffer.size_bytes(); + + // norms.resize() + norms.resize(normals->count); + std::memcpy(norms.data(), normals->buffer.get(), numVerticesBytesNorm); + } + + std::vector texco; + if (texcoords) { + const size_t numVerticesBytesTexCords = texcoords->buffer.size_bytes(); + texco.resize(texcoords->count); + std::memcpy(texco.data(), texcoords->buffer.get(), numVerticesBytesTexCords); + } + + for (size_t j = 0; j < vertices->count; ++j) { + + Vertex v; + v.position = XMFLOAT3(verts[j].x, verts[j].y, verts[j].z); + if(normals) + v.normal = XMFLOAT3(norms[j].x, norms[j].y, norms[j].z); + if(texcoords) + v.textureCoordinate = XMFLOAT2(texco[j].x, texco[j].y ); + + verticeData.push_back(v); + + } + + const size_t numVerticesBytesFaces = faces->buffer.size_bytes(); + long numIndicestest = numVerticesBytesFaces / 4 / 3;// int on 4 bytes + std::vector facer(faces->count); + std::memcpy(facer.data(), faces->buffer.get(), numVerticesBytesFaces); + + unsigned long numIndices = faces->count; + // Copy IB to result + std::unique_ptr indices(new (std::nothrow) uint32_t[numIndices * 3]); + if (!indices) + return E_OUTOFMEMORY; + + int index = 0; + for (size_t j = 0; j < (numIndices * 3); j += 3) { + + uint32_t pt0 = facer.at(index)[0]; + uint32_t pt1 = facer.at(index)[1]; + uint32_t pt2 = facer.at(index)[2]; + + indices[j] = pt0; + indices[j + 1] = pt1; + indices[j + 2] = pt2; + index++; + } + + + static const D3D11_INPUT_ELEMENT_DESC s_vboLayout[] = + { + { "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 }, + { "NORMAL", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 12, D3D11_INPUT_PER_VERTEX_DATA, 0 }, + { "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 24, D3D11_INPUT_PER_VERTEX_DATA, 0 }, + }; + + static const D3D11_INPUT_ELEMENT_DESC s_vboLayoutAlt[] = + { + { "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 }, + { "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 24, D3D11_INPUT_PER_VERTEX_DATA, 0 }, + }; + + const D3D11_INPUT_ELEMENT_DESC* layout = s_vboLayout; + size_t nDecl = std::size(s_vboLayout); + + if (!normals && !texcoords) + { + nDecl = 1; + } + else if (normals && !texcoords) + { + nDecl = 2; + } + else if (!normals && texcoords) + { + layout = s_vboLayoutAlt; + nDecl = std::size(s_vboLayoutAlt); + } + + HRESULT hr; + VBReader vbr; + hr = vbr.Initialize(layout, nDecl); + if (FAILED(hr)) + return hr; + + hr = vbr.AddStream(verts.data(), verts.size(), 0, sizeof(Vertex)); // WaveFrontReader::Vertex + if (FAILED(hr)) + return hr; + + hr = inMesh->SetVertexData(vbr, verts.size()); + if (FAILED(hr)) + return hr; + + hr = inMesh->SetIndexData(numIndices , (const uint32_t*)facer.data(), NULL); + if (FAILED(hr)) + return hr; + + + // attention !!!!! + // il y a aussi : uint32_t hr = inMesh->SetIndexData(numIndices , (const uint32_t*)facer.data(), NULL); + + /* + hr = inMesh->UpdateFaces(numIndices , (const uint32_t *) facer.data() ); + if (FAILED(hr)) + return hr; */ + + int dede = 0; + + + + + /* + inMesh->mPositions.swap(pos); + inMesh->mNormals.swap(norm); + inMesh->mTexCoords.swap(texcoord); + inMesh->mIndices.swap(indices); + inMesh->mnVerts = vertices->count; + inMesh->mnFaces = numIndices;// / 3; + + int dede = 0;*/ + + + + //result. + /* + // Example One: converting to your own application types + { + const size_t numVerticesBytes = vertices->buffer.size_bytes(); + std::vector verts(vertices->count); + std::memcpy(verts.data(), vertices->buffer.get(), numVerticesBytes); + } + + // Example Two: converting to your own application type + { + std::vector verts_floats; + std::vector verts_doubles; + if (vertices->t == tinyply::Type::FLOAT32) { } + if (vertices->t == tinyply::Type::FLOAT64) { } + }*/ + } + catch (const std::exception& e) + { + std::cerr << "Caught tinyply exception: " << e.what() << std::endl; + } + + + + + + /* + WaveFrontReader wfReader; + HRESULT hr = wfReader.Load(szFilename, ccw); + if (FAILED(hr)) + return hr; + + inMesh.reset(new (std::nothrow) Mesh); + if (!inMesh) + return E_OUTOFMEMORY; + + if (wfReader.indices.empty() || wfReader.vertices.empty()) + return E_FAIL; + + hr = inMesh->SetIndexData(wfReader.indices.size() / 3, wfReader.indices.data(), + wfReader.attributes.empty() ? nullptr : wfReader.attributes.data()); + if (FAILED(hr)) + return hr; + + static const D3D11_INPUT_ELEMENT_DESC s_vboLayout[] = + { + { "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 }, + { "NORMAL", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 12, D3D11_INPUT_PER_VERTEX_DATA, 0 }, + { "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 24, D3D11_INPUT_PER_VERTEX_DATA, 0 }, + }; + + static const D3D11_INPUT_ELEMENT_DESC s_vboLayoutAlt[] = + { + { "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 }, + { "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 24, D3D11_INPUT_PER_VERTEX_DATA, 0 }, + }; + + const D3D11_INPUT_ELEMENT_DESC* layout = s_vboLayout; + size_t nDecl = std::size(s_vboLayout); + + if (!wfReader.hasNormals && !wfReader.hasTexcoords) + { + nDecl = 1; + } + else if (wfReader.hasNormals && !wfReader.hasTexcoords) + { + nDecl = 2; + } + else if (!wfReader.hasNormals && wfReader.hasTexcoords) + { + layout = s_vboLayoutAlt; + nDecl = std::size(s_vboLayoutAlt); + } + + VBReader vbr; + hr = vbr.Initialize(layout, nDecl); + if (FAILED(hr)) + return hr; + + hr = vbr.AddStream(wfReader.vertices.data(), wfReader.vertices.size(), 0, sizeof(WaveFrontReader::Vertex)); + if (FAILED(hr)) + return hr; + + hr = inMesh->SetVertexData(vbr, wfReader.vertices.size()); + if (FAILED(hr)) + return hr; + + if (!wfReader.materials.empty()) + { + inMaterial.clear(); + inMaterial.reserve(wfReader.materials.size()); + + for (const auto& it : wfReader.materials) + { + Mesh::Material mtl = {}; + + mtl.name = it.strName; + mtl.specularPower = (it.bSpecular) ? float(it.nShininess) : 1.f; + mtl.alpha = it.fAlpha; + mtl.ambientColor = it.vAmbient; + mtl.diffuseColor = it.vDiffuse; + mtl.specularColor = (it.bSpecular) ? it.vSpecular : XMFLOAT3(0.f, 0.f, 0.f); + mtl.emissiveColor = (it.bEmissive) ? it.vEmissive : XMFLOAT3(0.f, 0.f, 0.f); + + mtl.texture = ProcessTextureFileName(it.strTexture, dds); + mtl.normalTexture = ProcessTextureFileName(it.strNormalTexture, dds); + mtl.specularTexture = ProcessTextureFileName(it.strSpecularTexture, dds); + if (it.bEmissive) + { + mtl.emissiveTexture = ProcessTextureFileName(it.strEmissiveTexture, dds); + } + mtl.rmaTexture = ProcessTextureFileName(it.strRMATexture, dds); + + inMaterial.push_back(mtl); + } + } + + if (wfReader.materials.size() > 1) + { + inMesh->SetMTLFileName(wfReader.name); + }*/ + + return S_OK; +} + +//-------------------------------------------------------------------------------------- +_Use_decl_annotations_ +HRESULT Mesh::ExportToPLY(const wchar_t* szFileNameIn/*, size_t nMaterials, const Material* materials*/) const +{ + + std::wstring szFileName( szFileNameIn ); + + using namespace VBO; + + + geometry cube;// = make_cube_geometry(); + + //if (!szFileName) + // return E_INVALIDARG; + + if (!mnFaces || !mIndices || !mnVerts || !mPositions || !mNormals || !mTexCoords) + return E_UNEXPECTED; + + if ((uint64_t(mnFaces) * 3) >= UINT32_MAX) + return HRESULT_FROM_WIN32(ERROR_ARITHMETIC_OVERFLOW); + + if (mnVerts >= UINT16_MAX) + return HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED); + + // Setup VBO header + header_t header; + header.numVertices = static_cast(mnVerts); + header.numIndices = static_cast(mnFaces * 3); + + // Setup vertices/indices for VBO + + std::unique_ptr vb(new (std::nothrow) vertex_t[mnVerts]); + std::unique_ptr ib(new (std::nothrow) uint16_t[header.numIndices]); + if (!vb || !ib) + return E_OUTOFMEMORY; + + + // Copy to VB + + for (size_t j = 0; j < mnVerts; ++j) + { + + float3 vert; + vert.x = mPositions[j].x; + vert.y = mPositions[j].y; + vert.z = mPositions[j].z; + + + float3 norm; + norm.x = mNormals[j].x; + norm.y = mNormals[j].y; + norm.z = mNormals[j].z; + + float2 tex; + tex.x = mTexCoords[j].x; + tex.y = mTexCoords[j].y; + + + cube.vertices.push_back(vert); + cube.normals.push_back(norm); + cube.texcoords.push_back(tex); + + } + + + + std::unique_ptr ib2(new (std::nothrow) uint16_t[header.numIndices]); + if (!vb || !ib) + return E_OUTOFMEMORY; + + + /* + // Copy to VB + auto vptr = vb.get(); + for (size_t j = 0; j < mnVerts; ++j, ++vptr) + { + vptr->position = mPositions[j]; + vptr->normal = mNormals[j]; + vptr->textureCoordinate = mTexCoords[j]; + }*/ + + // Copy to IB + /* + auto iptr = ib2.get(); + for (size_t j = 0; j < header.numIndices; ++j, ++iptr) + { + uint32_t index = mIndices[j]; + if (index == uint32_t(-1)) + { + *iptr = uint16_t(-1); + } + else if (index >= UINT16_MAX) + { + return HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED); + } + else + { + *iptr = static_cast(index); + } + }*/ + + + + + auto iptr = mIndices.get(); + for (size_t j = 0; j < mnFaces * 3; j += 3) + { + + + uint16_t indice1 = *iptr++; + uint16_t indice2 = *iptr++; + uint16_t indice3 = *iptr++; + + //cube.triangles.push_back({ indice1,indice2,indice3 }); + + cube.triangles.push_back({ mIndices[j],mIndices[j + 1],mIndices[j + 2] }); + + + } + + + + + + std::filebuf fb_binary; + fb_binary.open(szFileName + L"-binary.ply", std::ios::out | std::ios::binary); + std::ostream outstream_binary(&fb_binary); + if (outstream_binary.fail()) throw std::runtime_error("failed to open "); + + std::filebuf fb_ascii; + fb_ascii.open(szFileName + L"-ascii.ply", std::ios::out); + std::ostream outstream_ascii(&fb_ascii); + if (outstream_ascii.fail()) throw std::runtime_error("failed to open "); + + PlyFile cube_file; + + cube_file.add_properties_to_element("vertex", { "x", "y", "z" }, + Type::FLOAT32, cube.vertices.size(), reinterpret_cast(cube.vertices.data()), Type::INVALID, 0); + + cube_file.add_properties_to_element("vertex", { "nx", "ny", "nz" }, + Type::FLOAT32, cube.normals.size(), reinterpret_cast(cube.normals.data()), Type::INVALID, 0); + + cube_file.add_properties_to_element("vertex", { "u", "v" }, + Type::FLOAT32, cube.texcoords.size(), reinterpret_cast(cube.texcoords.data()), Type::INVALID, 0); + + cube_file.add_properties_to_element("face", { "vertex_indices" }, + Type::UINT32, cube.triangles.size(), reinterpret_cast(cube.triangles.data()), Type::UINT8, 3); + + cube_file.get_comments().push_back("generated by tinyply 2.3"); + + // Write an ASCII file + cube_file.write(outstream_ascii, false); + + // Write a binary file + cube_file.write(outstream_binary, true); + + + + + + +} + + + diff --git a/UVAtlasTool/tinyply.h b/UVAtlasTool/tinyply.h new file mode 100644 index 00000000..cbb1014f --- /dev/null +++ b/UVAtlasTool/tinyply.h @@ -0,0 +1,970 @@ +/* + * tinyply 2.3.2 (https://github.com/ddiakopoulos/tinyply) + * + * A single-header, zero-dependency (except the C++ STL) public domain implementation + * of the PLY mesh file format. Requires C++11; errors are handled through exceptions. + * + * This software is in the public domain. Where that dedication is not + * recognized, you are granted a perpetual, irrevocable license to copy, + * distribute, and modify this file as you see fit. + * + * Authored by Dimitri Diakopoulos (http://www.dimitridiakopoulos.com) + * + * tinyply.h may be included in many files, however in a single compiled file, + * the implementation must be created with the following defined prior to header inclusion + * #define TINYPLY_IMPLEMENTATION + * + */ + +//////////////////////// +// tinyply header // +//////////////////////// + +#ifndef tinyply_h +#define tinyply_h + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace tinyply +{ + + enum class Type : uint8_t + { + INVALID, + INT8, + UINT8, + INT16, + UINT16, + INT32, + UINT32, + FLOAT32, + FLOAT64 + }; + + struct PropertyInfo + { + PropertyInfo() {}; + PropertyInfo(int stride, std::string str) + : stride(stride), str(str) {} + int stride {0}; + std::string str; + }; + + static std::map PropertyTable + { + { Type::INT8, PropertyInfo(1, std::string("char")) }, + { Type::UINT8, PropertyInfo(1, std::string("uchar")) }, + { Type::INT16, PropertyInfo(2, std::string("short")) }, + { Type::UINT16, PropertyInfo(2, std::string("ushort")) }, + { Type::INT32, PropertyInfo(4, std::string("int")) }, + { Type::UINT32, PropertyInfo(4, std::string("uint")) }, + { Type::FLOAT32, PropertyInfo(4, std::string("float")) }, + { Type::FLOAT64, PropertyInfo(8, std::string("double")) }, + { Type::INVALID, PropertyInfo(0, std::string("INVALID"))} + }; + + class Buffer + { + uint8_t * alias{ nullptr }; + struct delete_array { void operator()(uint8_t * p) { delete[] p; } }; + std::unique_ptr data; + size_t size {0}; + public: + Buffer() {}; + Buffer(const size_t size) : data(new uint8_t[size], delete_array()), size(size) { alias = data.get(); } // allocating + Buffer(uint8_t * ptr): alias(ptr) { } // non-allocating, todo: set size? + uint8_t * get() { return alias; } + size_t size_bytes() const { return size; } + }; + + struct PlyData + { + Type t; + Buffer buffer; + size_t count {0}; + bool isList {false}; + }; + + struct PlyProperty + { + PlyProperty(std::istream & is); + PlyProperty(Type type, std::string & _name) : name(_name), propertyType(type) {} + PlyProperty(Type list_type, Type prop_type, std::string & _name, size_t list_count) + : name(_name), propertyType(prop_type), isList(true), listType(list_type), listCount(list_count) {} + std::string name; + Type propertyType{ Type::INVALID }; + bool isList{ false }; + Type listType{ Type::INVALID }; + size_t listCount {0}; + }; + + struct PlyElement + { + PlyElement(std::istream & istream); + PlyElement(const std::string & _name, size_t count) : name(_name), size(count) {} + std::string name; + size_t size {0}; + std::vector properties; + }; + + struct PlyFile + { + struct PlyFileImpl; + std::unique_ptr impl; + + PlyFile(); + ~PlyFile(); + + /* + * The ply format requires an ascii header. This can be used to determine at + * runtime which properties or elements exist in the file. Limited validation of the + * header is performed; it is assumed the header correctly reflects the contents of the + * payload. This function may throw. Returns true on success, false on failure. + */ + bool parse_header(std::istream & is); + + /* + * Execute a read operation. Data must be requested via `request_properties_from_element(...)` + * prior to calling this function. + */ + void read(std::istream & is); + + /* + * `write` performs no validation and assumes that the data passed into + * `add_properties_to_element` is well-formed. + */ + void write(std::ostream & os, bool isBinary); + + /* + * These functions are valid after a call to `parse_header(...)`. In the case of + * writing, get_comments() reference may also be used to add new comments to the ply header. + */ + std::vector get_elements() const; + std::vector get_info() const; + std::vector & get_comments(); + bool is_binary_file() const; + + /* + * In the general case where |list_size_hint| is zero, `read` performs a two-pass + * parse to support variable length lists. The most general use of the + * ply format is storing triangle meshes. When this fact is known a-priori, we can pass + * an expected list length that will apply to this element. Doing so results in an up-front + * memory allocation and a single-pass import, a 2x performance optimization. + */ + std::shared_ptr request_properties_from_element(const std::string & elementKey, + const std::vector propertyKeys, const uint32_t list_size_hint = 0); + + void add_properties_to_element(const std::string & elementKey, + const std::vector propertyKeys, + const Type type, + const size_t count, + uint8_t * data, + const Type listType, + const size_t listCount); + }; + +} // end namespace tinyply + +#endif // end tinyply_h + +//////////////////////////////// +// tinyply implementation // +//////////////////////////////// + +#ifdef TINYPLY_IMPLEMENTATION + +#include +#include +#include +#include +#include + +using namespace tinyply; +using namespace std; + +template inline T2 endian_swap(const T & v) noexcept { return v; } +template<> inline uint16_t endian_swap(const uint16_t & v) noexcept { return (v << 8) | (v >> 8); } +template<> inline uint32_t endian_swap(const uint32_t & v) noexcept { return (v << 24) | ((v << 8) & 0x00ff0000) | ((v >> 8) & 0x0000ff00) | (v >> 24); } +template<> inline uint64_t endian_swap(const uint64_t & v) noexcept +{ + return (((v & 0x00000000000000ffLL) << 56) | + ((v & 0x000000000000ff00LL) << 40) | + ((v & 0x0000000000ff0000LL) << 24) | + ((v & 0x00000000ff000000LL) << 8) | + ((v & 0x000000ff00000000LL) >> 8) | + ((v & 0x0000ff0000000000LL) >> 24) | + ((v & 0x00ff000000000000LL) >> 40) | + ((v & 0xff00000000000000LL) >> 56)); +} +template<> inline int16_t endian_swap(const int16_t & v) noexcept { uint16_t r = endian_swap(*(uint16_t*)&v); return *(int16_t*)&r; } +template<> inline int32_t endian_swap(const int32_t & v) noexcept { uint32_t r = endian_swap(*(uint32_t*)&v); return *(int32_t*)&r; } +template<> inline int64_t endian_swap(const int64_t & v) noexcept { uint64_t r = endian_swap(*(uint64_t*)&v); return *(int64_t*)&r; } +template<> inline float endian_swap(const uint32_t & v) noexcept { union { float f; uint32_t i; }; i = endian_swap(v); return f; } +template<> inline double endian_swap(const uint64_t & v) noexcept { union { double d; uint64_t i; }; i = endian_swap(v); return d; } + +inline uint32_t hash_fnv1a(const std::string & str) noexcept +{ + static const uint32_t fnv1aBase32 = 0x811C9DC5u; + static const uint32_t fnv1aPrime32 = 0x01000193u; + uint32_t result = fnv1aBase32; + for (auto & c : str) { result ^= static_cast(c); result *= fnv1aPrime32; } + return result; +} + +inline Type property_type_from_string(const std::string & t) noexcept +{ + if (t == "int8" || t == "char") return Type::INT8; + else if (t == "uint8" || t == "uchar") return Type::UINT8; + else if (t == "int16" || t == "short") return Type::INT16; + else if (t == "uint16" || t == "ushort") return Type::UINT16; + else if (t == "int32" || t == "int") return Type::INT32; + else if (t == "uint32" || t == "uint") return Type::UINT32; + else if (t == "float32" || t == "float") return Type::FLOAT32; + else if (t == "float64" || t == "double") return Type::FLOAT64; + return Type::INVALID; +} + +struct PlyFile::PlyFileImpl +{ + struct PlyDataCursor + { + size_t byteOffset{ 0 }; + size_t totalSizeBytes{ 0 }; + }; + + struct ParsingHelper + { + std::shared_ptr data; + std::shared_ptr cursor; + uint32_t list_size_hint; + }; + + struct PropertyLookup + { + ParsingHelper * helper{ nullptr }; + bool skip{ false }; + size_t prop_stride{ 0 }; // precomputed + size_t list_stride{ 0 }; // precomputed + }; + + std::unordered_map userData; + + bool isBinary = false; + bool isBigEndian = false; + std::vector elements; + std::vector comments; + std::vector objInfo; + uint8_t scratch[64]; // large enough for max list size + + void read(std::istream & is); + void write(std::ostream & os, bool isBinary); + + std::shared_ptr request_properties_from_element(const std::string & elementKey, + const std::vector propertyKeys, + const uint32_t list_size_hint); + + void add_properties_to_element(const std::string & elementKey, + const std::vector propertyKeys, + const Type type, const size_t count, uint8_t * data, const Type listType, const size_t listCount); + + size_t read_property_binary(const size_t & stride, void * dest, size_t & destOffset, std::istream & is) noexcept; + size_t read_property_ascii(const Type & t, const size_t & stride, void * dest, size_t & destOffset, std::istream & is); + + std::vector> make_property_lookup_table(); + + bool parse_header(std::istream & is); + void parse_data(std::istream & is, bool firstPass); + void read_header_format(std::istream & is); + void read_header_element(std::istream & is); + void read_header_property(std::istream & is); + void read_header_text(std::string line, std::vector & place, int erase = 0); + + void write_header(std::ostream & os) noexcept; + void write_ascii_internal(std::ostream & os) noexcept; + void write_binary_internal(std::ostream & os) noexcept; + void write_property_ascii(Type t, std::ostream & os, uint8_t * src, size_t & srcOffset); + void write_property_binary(std::ostream & os, uint8_t * src, size_t & srcOffset, const size_t & stride) noexcept; +}; + +PlyProperty::PlyProperty(std::istream & is) : isList(false) +{ + std::string type; + is >> type; + if (type == "list") + { + std::string countType; + is >> countType >> type; + listType = property_type_from_string(countType); + isList = true; + } + propertyType = property_type_from_string(type); + is >> name; +} + +PlyElement::PlyElement(std::istream & is) +{ + is >> name >> size; +} + +template inline T ply_read_ascii(std::istream & is) +{ + T data; + is >> data; + return data; +} + +template +inline void endian_swap_buffer(uint8_t * data_ptr, const size_t num_bytes, const size_t stride) +{ + for (size_t count = 0; count < num_bytes; count += stride) + { + *(reinterpret_cast(data_ptr)) = endian_swap(*(reinterpret_cast(data_ptr))); + data_ptr += stride; + } +} + +template void ply_cast_ascii(void * dest, std::istream & is) +{ + *(static_cast(dest)) = ply_read_ascii(is); +} + +int64_t find_element(const std::string & key, const std::vector & list) +{ + for (size_t i = 0; i < list.size(); i++) if (list[i].name == key) return i; + return -1; +} + +int64_t find_property(const std::string & key, const std::vector & list) +{ + for (size_t i = 0; i < list.size(); ++i) if (list[i].name == key) return i; + return -1; +} + +// The `userData` table is an easy data structure for capturing what data the +// user would like out of the ply file, but an inner-loop hash lookup is non-ideal. +// The property lookup table flattens the table down into a 2D array optimized +// for parsing. The first index is the element, and the second index is the property. +std::vector> PlyFile::PlyFileImpl::make_property_lookup_table() +{ + std::vector> element_property_lookup; + + for (auto & element : elements) + { + std::vector lookups; + + for (auto & property : element.properties) + { + PropertyLookup f; + + auto cursorIt = userData.find(hash_fnv1a(element.name + property.name)); + if (cursorIt != userData.end()) f.helper = &cursorIt->second; + else f.skip = true; + + f.prop_stride = PropertyTable[property.propertyType].stride; + if (property.isList) f.list_stride = PropertyTable[property.listType].stride; + + lookups.push_back(f); + } + + element_property_lookup.push_back(lookups); + } + + return element_property_lookup; +} + +bool PlyFile::PlyFileImpl::parse_header(std::istream & is) +{ + std::string line; + bool success = true; + while (std::getline(is, line)) + { + std::istringstream ls(line); + std::string token; + ls >> token; + if (token == "ply" || token == "PLY" || token == "") continue; + else if (token == "comment") read_header_text(line, comments, 8); + else if (token == "format") read_header_format(ls); + else if (token == "element") read_header_element(ls); + else if (token == "property") read_header_property(ls); + else if (token == "obj_info") read_header_text(line, objInfo, 9); + else if (token == "end_header") break; + else success = false; // unexpected header field + } + return success; +} + +void PlyFile::PlyFileImpl::read_header_text(std::string line, std::vector& place, int erase) +{ + place.push_back((erase > 0) ? line.erase(0, erase) : line); +} + +void PlyFile::PlyFileImpl::read_header_format(std::istream & is) +{ + std::string s; + (is >> s); + if (s == "binary_little_endian") isBinary = true; + else if (s == "binary_big_endian") isBinary = isBigEndian = true; +} + +void PlyFile::PlyFileImpl::read_header_element(std::istream & is) +{ + elements.emplace_back(is); +} + +void PlyFile::PlyFileImpl::read_header_property(std::istream & is) +{ + if (!elements.size()) throw std::runtime_error("no elements defined; file is malformed"); + elements.back().properties.emplace_back(is); +} + +size_t PlyFile::PlyFileImpl::read_property_binary(const size_t & stride, void * dest, size_t & destOffset, std::istream & is) noexcept +{ + destOffset += stride; + is.read((char*)dest, stride); + return stride; +} + +size_t PlyFile::PlyFileImpl::read_property_ascii(const Type & t, const size_t & stride, void * dest, size_t & destOffset, std::istream & is) +{ + destOffset += stride; + switch (t) + { + case Type::INT8: *((int8_t *)dest) = static_cast(ply_read_ascii(is)); break; + case Type::UINT8: *((uint8_t *)dest) = static_cast(ply_read_ascii(is)); break; + case Type::INT16: ply_cast_ascii(dest, is); break; + case Type::UINT16: ply_cast_ascii(dest, is); break; + case Type::INT32: ply_cast_ascii(dest, is); break; + case Type::UINT32: ply_cast_ascii(dest, is); break; + case Type::FLOAT32: ply_cast_ascii(dest, is); break; + case Type::FLOAT64: ply_cast_ascii(dest, is); break; + case Type::INVALID: throw std::invalid_argument("invalid ply property"); + } + return stride; +} + +void PlyFile::PlyFileImpl::write_property_ascii(Type t, std::ostream & os, uint8_t * src, size_t & srcOffset) +{ + switch (t) + { + case Type::INT8: os << static_cast(*reinterpret_cast(src)); break; + case Type::UINT8: os << static_cast(*reinterpret_cast(src)); break; + case Type::INT16: os << *reinterpret_cast(src); break; + case Type::UINT16: os << *reinterpret_cast(src); break; + case Type::INT32: os << *reinterpret_cast(src); break; + case Type::UINT32: os << *reinterpret_cast(src); break; + case Type::FLOAT32: os << *reinterpret_cast(src); break; + case Type::FLOAT64: os << *reinterpret_cast(src); break; + case Type::INVALID: throw std::invalid_argument("invalid ply property"); + } + os << " "; + srcOffset += PropertyTable[t].stride; +} + +void PlyFile::PlyFileImpl::write_property_binary(std::ostream & os, uint8_t * src, size_t & srcOffset, const size_t & stride) noexcept +{ + os.write((char *)src, stride); + srcOffset += stride; +} + +void PlyFile::PlyFileImpl::read(std::istream & is) +{ + std::vector> buffers; + for (auto & entry : userData) buffers.push_back(entry.second.data); + + // Discover if we can allocate up front without parsing the file twice + uint32_t list_hints = 0; + for (auto & b : buffers) for (auto & entry : userData) {list_hints += entry.second.list_size_hint;(void)b;} + + // No list hints? Then we need to calculate how much memory to allocate + if (list_hints == 0) + { + parse_data(is, true); + } + + // Count the number of properties (required for allocation) + // e.g. if we have properties x y and z requested, we ensure + // that their buffer points to the same PlyData + std::unordered_map unique_data_count; + for (auto & ptr : buffers) unique_data_count[ptr.get()] += 1; + + // Since group-requested properties share the same cursor, + // we need to find unique cursors so we only allocate once + std::sort(buffers.begin(), buffers.end()); + buffers.erase(std::unique(buffers.begin(), buffers.end()), buffers.end()); + + // We sorted by ptrs on PlyData, need to remap back onto its cursor in the userData table + for (auto & b : buffers) + { + for (auto & entry : userData) + { + if (entry.second.data == b && b->buffer.get() == nullptr) + { + // If we didn't receive any list hints, it means we did two passes over the + // file to compute the total length of all (potentially) variable-length lists + if (list_hints == 0) + { + b->buffer = Buffer(entry.second.cursor->totalSizeBytes); + } + else + { + // otherwise, we can allocate up front, skipping the first pass. + const size_t list_size_multiplier = (entry.second.data->isList ? entry.second.list_size_hint : 1); + auto bytes_per_property = entry.second.data->count * PropertyTable[entry.second.data->t].stride * list_size_multiplier; + bytes_per_property *= unique_data_count[b.get()]; + b->buffer = Buffer(bytes_per_property); + } + + } + } + } + + // Populate the data + parse_data(is, false); + + // In-place big-endian to little-endian swapping if required + if (isBigEndian) + { + for (auto & b : buffers) + { + uint8_t * data_ptr = b->buffer.get(); + const size_t stride = PropertyTable[b->t].stride; + const size_t buffer_size_bytes = b->buffer.size_bytes(); + + switch (b->t) + { + case Type::INT16: endian_swap_buffer(data_ptr, buffer_size_bytes, stride); break; + case Type::UINT16: endian_swap_buffer(data_ptr, buffer_size_bytes, stride); break; + case Type::INT32: endian_swap_buffer(data_ptr, buffer_size_bytes, stride); break; + case Type::UINT32: endian_swap_buffer(data_ptr, buffer_size_bytes, stride); break; + case Type::FLOAT32: endian_swap_buffer(data_ptr, buffer_size_bytes, stride); break; + case Type::FLOAT64: endian_swap_buffer(data_ptr, buffer_size_bytes, stride); break; + default: break; + } + } + } +} + +void PlyFile::PlyFileImpl::write(std::ostream & os, bool _isBinary) +{ + for (auto & d : userData) { d.second.cursor->byteOffset = 0; } + if (_isBinary) + { + isBinary = true; + isBigEndian = false; + write_binary_internal(os); + } + else + { + isBinary = false; + isBigEndian = false; + write_ascii_internal(os); + } +} + +void PlyFile::PlyFileImpl::write_binary_internal(std::ostream & os) noexcept +{ + isBinary = true; + + write_header(os); + + uint8_t listSize[4] = { 0, 0, 0, 0 }; + size_t dummyCount = 0; + + auto element_property_lookup = make_property_lookup_table(); + + size_t element_idx = 0; + for (auto & e : elements) + { + for (size_t i = 0; i < e.size; ++i) + { + size_t property_index = 0; + for (auto & p : e.properties) + { + auto & f = element_property_lookup[element_idx][property_index]; + auto * helper = f.helper; + if (f.skip || helper == nullptr) continue; + + if (p.isList) + { + std::memcpy(listSize, &p.listCount, sizeof(uint32_t)); + write_property_binary(os, listSize, dummyCount, f.list_stride); + write_property_binary(os, (helper->data->buffer.get() + helper->cursor->byteOffset), helper->cursor->byteOffset, f.prop_stride * p.listCount); + } + else + { + write_property_binary(os, (helper->data->buffer.get() + helper->cursor->byteOffset), helper->cursor->byteOffset, f.prop_stride); + } + property_index++; + } + } + element_idx++; + } +} + +void PlyFile::PlyFileImpl::write_ascii_internal(std::ostream & os) noexcept +{ + write_header(os); + + auto element_property_lookup = make_property_lookup_table(); + + size_t element_idx = 0; + for (auto & e : elements) + { + for (size_t i = 0; i < e.size; ++i) + { + size_t property_index = 0; + for (auto & p : e.properties) + { + auto & f = element_property_lookup[element_idx][property_index]; + auto * helper = f.helper; + if (f.skip || helper == nullptr) continue; + + if (p.isList) + { + os << p.listCount << " "; + for (size_t j = 0; j < p.listCount; ++j) + { + write_property_ascii(p.propertyType, os, (helper->data->buffer.get() + helper->cursor->byteOffset), helper->cursor->byteOffset); + } + } + else + { + write_property_ascii(p.propertyType, os, (helper->data->buffer.get() + helper->cursor->byteOffset), helper->cursor->byteOffset); + } + property_index++; + } + os << "\n"; + } + element_idx++; + } +} + +void PlyFile::PlyFileImpl::write_header(std::ostream & os) noexcept +{ + const std::locale & fixLoc = std::locale("C"); + os.imbue(fixLoc); + + os << "ply\n"; + if (isBinary) os << ((isBigEndian) ? "format binary_big_endian 1.0" : "format binary_little_endian 1.0") << "\n"; + else os << "format ascii 1.0\n"; + + for (const auto & comment : comments) os << "comment " << comment << "\n"; + + auto property_lookup = make_property_lookup_table(); + + size_t element_idx = 0; + for (auto & e : elements) + { + os << "element " << e.name << " " << e.size << "\n"; + size_t property_idx = 0; + for (const auto & p : e.properties) + { + PropertyLookup & lookup = property_lookup[element_idx][property_idx]; + + if (!lookup.skip) + { + if (p.isList) + { + os << "property list " << PropertyTable[p.listType].str << " " + << PropertyTable[p.propertyType].str << " " << p.name << "\n"; + } + else + { + os << "property " << PropertyTable[p.propertyType].str << " " << p.name << "\n"; + } + } + property_idx++; + } + element_idx++; + } + os << "end_header\n"; +} + +std::shared_ptr PlyFile::PlyFileImpl::request_properties_from_element(const std::string & elementKey, + const std::vector propertyKeys, + const uint32_t list_size_hint) +{ + if (elements.empty()) throw std::runtime_error("header had no elements defined. malformed file?"); + if (elementKey.empty()) throw std::invalid_argument("`elementKey` argument is empty"); + if (propertyKeys.empty()) throw std::invalid_argument("`propertyKeys` argument is empty"); + + std::shared_ptr out_data = std::make_shared(); + + const int64_t elementIndex = find_element(elementKey, elements); + + std::vector keys_not_found; + + // Sanity check if the user requested element is in the pre-parsed header + if (elementIndex >= 0) + { + // We found the element + const PlyElement & element = elements[elementIndex]; + + // Each key in `propertyKey` gets an entry into the userData map (keyed by a hash of + // element name and property name), but groups of properties (requested from the + // public api through this function) all share the same `ParsingHelper`. When it comes + // time to .read(), we check the number of unique PlyData shared pointers + // and allocate a single buffer that will be used by each property key group. + // That way, properties like, {"x", "y", "z"} will all be put into the same buffer. + + ParsingHelper helper; + helper.data = out_data; + helper.data->count = element.size; // how many items are in the element? + helper.data->isList = false; + helper.data->t = Type::INVALID; + helper.cursor = std::make_shared(); + helper.list_size_hint = list_size_hint; + + // Find each of the keys + for (const auto & key : propertyKeys) + { + const int64_t propertyIndex = find_property(key, element.properties); + if (propertyIndex < 0) keys_not_found.push_back(key); + } + + if (keys_not_found.size()) + { + std::stringstream ss; + for (auto & str : keys_not_found) ss << str << ", "; + throw std::invalid_argument("the following property keys were not found in the header: " + ss.str()); + } + + for (const auto & key : propertyKeys) + { + const int64_t propertyIndex = find_property(key, element.properties); + const PlyProperty & property = element.properties[propertyIndex]; + helper.data->t = property.propertyType; + helper.data->isList = property.isList; + auto result = userData.insert(std::pair(hash_fnv1a(element.name + property.name), helper)); + if (result.second == false) + { + throw std::invalid_argument("element-property key has already been requested: " + element.name + " " + property.name); + } + } + + // Sanity check that all properties share the same type + std::vector propertyTypes; + for (const auto & key : propertyKeys) + { + const int64_t propertyIndex = find_property(key, element.properties); + const PlyProperty & property = element.properties[propertyIndex]; + propertyTypes.push_back(property.propertyType); + } + + if (std::adjacent_find(propertyTypes.begin(), propertyTypes.end(), std::not_equal_to()) != propertyTypes.end()) + { + throw std::invalid_argument("all requested properties must share the same type."); + } + } + else throw std::invalid_argument("the element key was not found in the header: " + elementKey); + + return out_data; +} + +void PlyFile::PlyFileImpl::add_properties_to_element(const std::string & elementKey, + const std::vector propertyKeys, + const Type type, const size_t count, uint8_t * data, const Type listType, const size_t listCount) +{ + ParsingHelper helper; + helper.data = std::make_shared(); + helper.data->count = count; + helper.data->t = type; + helper.data->buffer = Buffer(data); // we should also set size for safety reasons + helper.cursor = std::make_shared(); + + auto create_property_on_element = [&](PlyElement & e) + { + for (auto key : propertyKeys) + { + PlyProperty newProp = (listType == Type::INVALID) ? PlyProperty(type, key) : PlyProperty(listType, type, key, listCount); + userData.insert(std::pair(hash_fnv1a(elementKey + key), helper)); + e.properties.push_back(newProp); + } + }; + + const int64_t idx = find_element(elementKey, elements); + if (idx >= 0) + { + PlyElement & e = elements[idx]; + create_property_on_element(e); + } + else + { + PlyElement newElement = (listType == Type::INVALID) ? PlyElement(elementKey, count) : PlyElement(elementKey, count); + create_property_on_element(newElement); + elements.push_back(newElement); + } +} + +void PlyFile::PlyFileImpl::parse_data(std::istream & is, bool firstPass) +{ + std::function read; + std::function skip; + + const auto start = is.tellg(); + + uint32_t listSize = 0; + size_t dummyCount = 0; + std::string skip_ascii_buffer; + + // Special case mirroring read_property_binary but for list types; this + // has an additional big endian check to flip the data in place immediately + // after reading. We do this as a performance optimization; endian flipping is + // done on regular properties as a post-process after reading (also for optimization) + // but we need the correct little-endian list count as we read the file. + auto read_list_binary = [this](const Type & t, void * dst, size_t & destOffset, const size_t & stride, std::istream & _is) noexcept + { + destOffset += stride; + _is.read((char*)dst, stride); + + if (isBigEndian) + { + switch (t) + { + case Type::INT16: *(int16_t*)dst = endian_swap(*(int16_t*)dst); break; + case Type::UINT16: *(uint16_t*)dst = endian_swap(*(uint16_t*)dst); break; + case Type::INT32: *(int32_t*)dst = endian_swap(*(int32_t*)dst); break; + case Type::UINT32: *(uint32_t*)dst = endian_swap(*(uint32_t*)dst); break; + default: break; + } + } + + return stride; + }; + + if (isBinary) + { + read = [this, &listSize, &dummyCount, &read_list_binary](PropertyLookup & f, const PlyProperty & p, uint8_t * dest, size_t & destOffset, std::istream & _is) noexcept + { + if (!p.isList) + { + return read_property_binary(f.prop_stride, dest + destOffset, destOffset, _is); + } + read_list_binary(p.listType, &listSize, dummyCount, f.list_stride, _is); // the list size + return read_property_binary(f.prop_stride * listSize, dest + destOffset, destOffset, _is); // properties in list + }; + skip = [this, &listSize, &dummyCount, &read_list_binary](PropertyLookup & f, const PlyProperty & p, std::istream & _is) noexcept + { + if (!p.isList) + { + _is.read((char*)scratch, f.prop_stride); + return f.prop_stride; + } + read_list_binary(p.listType, &listSize, dummyCount, f.list_stride, _is); // the list size (does not count for memory alloc) + auto bytes_to_skip = f.prop_stride * listSize; + _is.ignore(bytes_to_skip); + return bytes_to_skip; + }; + } + else + { + read = [this, &listSize, &dummyCount](PropertyLookup & f, const PlyProperty & p, uint8_t * dest, size_t & destOffset, std::istream & _is) noexcept + { + if (!p.isList) + { + read_property_ascii(p.propertyType, f.prop_stride, dest + destOffset, destOffset, _is); + } + else + { + read_property_ascii(p.listType, f.list_stride, &listSize, dummyCount, _is); // the list size + for (size_t i = 0; i < listSize; ++i) + { + read_property_ascii(p.propertyType, f.prop_stride, dest + destOffset, destOffset, _is); + } + } + }; + skip = [this, &listSize, &dummyCount, &skip_ascii_buffer](PropertyLookup & f, const PlyProperty & p, std::istream & _is) noexcept + { + skip_ascii_buffer.clear(); + if (p.isList) + { + read_property_ascii(p.listType, f.list_stride, &listSize, dummyCount, _is); // the list size (does not count for memory alloc) + for (size_t i = 0; i < listSize; ++i) _is >> skip_ascii_buffer; // properties in list + return listSize * f.prop_stride; + } + _is >> skip_ascii_buffer; + return f.prop_stride; + }; + } + + std::vector> element_property_lookup = make_property_lookup_table(); + size_t element_idx = 0; + size_t property_idx = 0; + ParsingHelper * helper {nullptr}; + + // This is the inner import loop + for (auto & element : elements) + { + for (size_t count = 0; count < element.size; ++count) + { + property_idx = 0; + for (auto & property : element.properties) + { + PropertyLookup & lookup = element_property_lookup[element_idx][property_idx]; + + if (!lookup.skip) + { + helper = lookup.helper; + if (firstPass) + { + helper->cursor->totalSizeBytes += skip(lookup, property, is); + + // These lines will be changed when tinyply supports + // variable length lists. We add it here so our header data structure + // contains enough info to write it back out again (e.g. transcoding). + if (property.listCount == 0) property.listCount = listSize; + if (property.listCount != listSize) throw std::runtime_error("variable length lists are not supported yet."); + } + else + { + read(lookup, property, helper->data->buffer.get(), helper->cursor->byteOffset, is); + } + } + else + { + skip(lookup, property, is); + } + property_idx++; + } + } + element_idx++; + } + + // Reset istream position to the start of the data + if (firstPass) is.seekg(start, is.beg); +} + +// Wrap the public interface: + +PlyFile::PlyFile() { impl.reset(new PlyFileImpl()); } +PlyFile::~PlyFile() { } +bool PlyFile::parse_header(std::istream & is) { return impl->parse_header(is); } +void PlyFile::read(std::istream & is) { return impl->read(is); } +void PlyFile::write(std::ostream & os, bool isBinary) { return impl->write(os, isBinary); } +std::vector PlyFile::get_elements() const { return impl->elements; } +std::vector & PlyFile::get_comments() { return impl->comments; } +std::vector PlyFile::get_info() const { return impl->objInfo; } +bool PlyFile::is_binary_file() const { return impl->isBinary; } +std::shared_ptr PlyFile::request_properties_from_element(const std::string & elementKey, + const std::vector propertyKeys, + const uint32_t list_size_hint) +{ + return impl->request_properties_from_element(elementKey, propertyKeys, list_size_hint); +} +void PlyFile::add_properties_to_element(const std::string & elementKey, + const std::vector propertyKeys, + const Type type, const size_t count, uint8_t * data, const Type listType, const size_t listCount) +{ + return impl->add_properties_to_element(elementKey, propertyKeys, type, count, data, listType, listCount); +} + +#endif // end TINYPLY_IMPLEMENTATION \ No newline at end of file