diff --git a/openVulkanoCpp/IO/Archive/ZipWriter.cpp b/openVulkanoCpp/IO/Archive/ZipWriter.cpp new file mode 100644 index 0000000..b4b8a4f --- /dev/null +++ b/openVulkanoCpp/IO/Archive/ZipWriter.cpp @@ -0,0 +1,260 @@ +/* + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +/* References: + * https://libzip.org/specifications/extrafld.txt + * https://users.cs.jmu.edu/buchhofp/forensics/formats/pkzip.html + * https://learn.microsoft.com/en-us/windows/win32/sysinfo/converting-a-time-t-value-to-a-file-time +*/ + +#include "ZipWriter.hpp" +#include "Base/Utils.hpp" +#include "IO/MemMappedFile.hpp" +#include "Math/CRC32.hpp" + +#include +#include + +namespace +{ +#pragma pack(push, 1) + struct LocalFileHeader + { + uint32_t signature = 0x04034b50; + uint16_t versionToExtract = 10; // Version needed to extract (minimum) + uint16_t generalPurposeFlags = 0; // General purpose bit flag + uint16_t compressionMethod = 0; // Compression method + uint16_t fileLastModTime = 0; // File last modification time + uint16_t fileLastModDate = 0; // File last modification date + uint32_t crc32 = 0; // CRC-32 + uint32_t compressedSize = 0; // Compressed size + uint32_t uncompressedSize = 0; // Uncompressed size + uint16_t fileNameLength = 0; // File name length (n) + uint16_t extraFieldLength = 0; // Extra field length (m) + // File Name[n] + // Extra Field[m] + }; + + struct CentalDirectoryFileHeader + { + uint32_t signature = 0x02014b50; + uint16_t versionMadeBy = 31; // Version made by + uint16_t versionToExtract = 10; // Version needed to extract (minimum) + uint16_t generalPurposeFlags = 0; // General purpose bit flag + uint16_t compressionMethod = 0; // Compression method + uint16_t fileLastModTime = 0; // File last modification time + uint16_t fileLastModDate = 0; // File last modification date + uint32_t crc32 = 0; // CRC-32 + uint32_t compressedSize = 0; // Compressed size + uint32_t uncompressedSize = 0; // Uncompressed size + uint16_t fileNameLength = 0; // File name length (n) + uint16_t extraFieldLength = 0; // Extra field length (m) + uint16_t fileCommentLength = 0; // File comment length (k) + uint16_t diskNumber = 0; // Disk number where file starts + uint16_t internalFileAttribs = 0; // Internal file attributes + uint32_t externalFileAttribs = 0; // External file attributes + uint32_t relativeOffsetOfLocalFileHeader = 0; // Relative offset of local file header. This is the number of bytes between the start of the first disk on which the file occurs, and the start of the local file header + // File Name[n] + // Extra Field[m] + // File Comment[k] + }; + + struct NtfsExtraField + { + uint16_t tag = 10; // Tag for this "extra" block type + uint16_t tSize = 32; // Total Data Size for this block + uint32_t reserved = 0; // for future use + uint16_t tag1 = 1; // NTFS attribute tag value #1 + uint16_t tSize1 = 24; // Size of attribute #1, in bytes + uint64_t modTime; // 64-bit NTFS file last modification time + uint64_t acTime; // 64-bit NTFS file last access time + uint64_t crTime; // 64-bit NTFS file creation time + + static uint64_t TimetToWinFileTime(time_t t) + { + // See references for details + return (t * 10000000LL) + 116444736000000000LL; + } + + NtfsExtraField(time_t modTime, time_t acTime, time_t crTime) + { + this->modTime = TimetToWinFileTime(modTime); + this->acTime = TimetToWinFileTime(acTime); + this->crTime = TimetToWinFileTime(crTime); + } + }; + + struct EndOfCentralDirectoryHeader + { + uint32_t signature = 0x06054b50; + uint16_t diskNumber = 0; // Number of this disk + uint16_t centralDirectoryDiskNumber = 0; // Disk where central directory starts + uint16_t centralDirectoryEntries = 0; // Number of central directory records on this disk + uint16_t totalCentralDirectoryEntries = 0; // Total number of central directory records + uint32_t centralDirectorySize = 0; // Size of central directory (bytes) + uint32_t centralDirectoryOffset = 0; // Offset of start of central directory, relative to start of archive + uint16_t commentLength = 0; // Comment length (n) + // Comment[n] + }; +#pragma pack(pop) + + static_assert(sizeof(LocalFileHeader) == 30, "Well packed struct"); + static_assert(sizeof(CentalDirectoryFileHeader) == 46, "Well packed struct"); + static_assert(sizeof(EndOfCentralDirectoryHeader) == 22, "Well packed struct"); + static_assert(sizeof(NtfsExtraField) == 36, "Well packed struct"); + + uint32_t Cat(std::vector& dest, size_t size, const uint8_t* thing) + { + uint32_t startOffset = dest.size(); + dest.insert(dest.end(), thing, thing + size); + return startOffset; + } + + template + uint32_t Cat(std::vector& dest, const T* thing) + { + return Cat(dest, sizeof(T), reinterpret_cast(thing)); + } + + uint32_t Cat(std::vector& dest, const std::vector& thing) + { + return Cat(dest, thing.size(), thing.data()); + } + + std::pair ConvertToDosTimeDate(const time_t time) + { + std::tm tm; +#if _MSC_VER >= 1400 + if (gmtime_s(&tm, &time) != 0) [[unlikely]] + throw std::runtime_error("gmtime_s() failed"); +#else + if (gmtime_r(&time, &tm) == nullptr) [[unlikely]] + throw std::runtime_error("gmtime_r() failed"); +#endif + uint16_t dosTime = 0; + dosTime |= (tm.tm_sec / 2) & 0x1F; // Seconds divided by 2 (Bits 00-04) + dosTime |= (tm.tm_min & 0x3F) << 5; // Minutes (Bits 05-10) + dosTime |= (tm.tm_hour & 0x1F) << 11; // Hours (Bits 11-15) + + uint16_t dosDate = 0; + dosDate |= (tm.tm_mday & 0x1F); // Day (Bits 00-04) + dosDate |= ((tm.tm_mon + 1) & 0x0F) << 5; // Month (Bits 05-08) + dosDate |= ((tm.tm_year - 80) & 0x7F) << 9; // Year from 1980 (Bits 09-15) + return { dosTime, dosDate }; + } + + template + void ZFill(FILE *handle, size_t num) + { + uint8_t zeros[MAX_PADDING_SIZE] = {}; + fwrite(&zeros, num, 1, handle); + } +} + +namespace OpenVulkano +{ + ZipWriter::ZipWriter(const std::filesystem::path& filePath, bool alignHeadersby64) + { + m_file = fopen(filePath.string().c_str(), "wb"); + if (!m_file) + { + throw std::runtime_error("Unable to open file for writing: " + filePath.string()); + } + m_pad = alignHeadersby64; + } + + void ZipWriter::AddFile(const FileDescription& description, const void* buffer) + { + size_t fileSize = description.size; + size_t fileNameLength = description.path.size(); + uint8_t *fileName = (uint8_t*)description.path.data(); + CRC32 crc; + crc.Update(fileSize, (const uint8_t *)buffer); + uint32_t crc32 = crc.GetValue(); + time_t createTime = description.createTime; + time_t modTime = description.modTime; + time_t accessTime = modTime; // FileDescription doesn't have this field + auto [dosTime, dosDate] = ConvertToDosTimeDate(modTime); + + LocalFileHeader lfh; + lfh.fileLastModTime = dosTime; + lfh.fileLastModDate = dosDate; + lfh.crc32 = crc32; + lfh.compressedSize = lfh.uncompressedSize = fileSize; + lfh.fileNameLength = fileNameLength; + + if (m_pad) + { + size_t headerSize = sizeof(LocalFileHeader) + fileNameLength; + size_t padding = 64 - ((ftell(m_file) + headerSize) & 63); + if (padding < 4) padding += 64; + lfh.extraFieldLength = padding; + } + + size_t headerOffset = ftell(m_file); + fwrite(&lfh, sizeof(lfh), 1, m_file); + fwrite(fileName, fileNameLength, 1, m_file); + + if (m_pad) + { + uint16_t marker = 6534; + uint16_t dataSize = lfh.extraFieldLength - 4; + fwrite(&marker, sizeof(uint16_t), 1, m_file); + fwrite(&dataSize, sizeof(uint16_t), 1, m_file); + ZFill<128>(m_file, dataSize); + } + + fwrite(buffer, fileSize, 1, m_file); + + CentalDirectoryFileHeader cdfh; + cdfh.fileLastModTime = dosTime; + cdfh.fileLastModDate = dosDate; + cdfh.crc32 = crc32; + cdfh.compressedSize = cdfh.uncompressedSize = fileSize; + cdfh.fileNameLength = fileNameLength; + cdfh.extraFieldLength = sizeof(NtfsExtraField); + cdfh.externalFileAttribs = 32; // NOTE(vb): I've no idea wtf is this value mean + cdfh.relativeOffsetOfLocalFileHeader = headerOffset; + + NtfsExtraField ntfs(modTime, accessTime, createTime); + + Cat(m_centralDirs, &cdfh); + Cat(m_centralDirs, fileNameLength, fileName); + Cat(m_centralDirs, &ntfs); + + m_numFiles += 1; + } + + void ZipWriter::AddFile(const std::filesystem::path& fileName, const char* inArchiveName) + { + MemMappedFile file = MemMappedFile(fileName.string()); + auto desc = OpenVulkano::FileDescription::MakeDescriptionForFile(inArchiveName, file.Size()); + AddFile(desc, file.Data()); + } + + ZipWriter::~ZipWriter() + { + if (IsOpen()) + { + int centralDirsOffset = 0; + size_t centralDirsSize = m_centralDirs.size(); + if (m_numFiles) + { + centralDirsOffset = ftell(m_file); + fwrite(m_centralDirs.data(), centralDirsSize, 1, m_file); + m_centralDirs.clear(); + } + + EndOfCentralDirectoryHeader eocd; + eocd.centralDirectoryEntries = eocd.totalCentralDirectoryEntries = m_numFiles; + eocd.centralDirectoryOffset = centralDirsOffset; + eocd.centralDirectorySize = centralDirsSize; + + fwrite(&eocd, sizeof(eocd), 1, m_file); + fclose(m_file); + } + } +} \ No newline at end of file diff --git a/openVulkanoCpp/IO/Archive/ZipWriter.hpp b/openVulkanoCpp/IO/Archive/ZipWriter.hpp new file mode 100644 index 0000000..d93e6f1 --- /dev/null +++ b/openVulkanoCpp/IO/Archive/ZipWriter.hpp @@ -0,0 +1,33 @@ +/* + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ +#pragma once + +#include "IO/FileDescription.hpp" + +#include +#include +#include + +namespace OpenVulkano +{ + class ZipWriter + { + std::vector m_centralDirs; + int m_numFiles = 0; + bool m_pad = false; + FILE* m_file; + + public: + ZipWriter(const std::filesystem::path& filePath, bool alignHeadersby64 = false); + ~ZipWriter(); + + bool IsOpen() const { return m_file != nullptr; } + operator bool() const { return IsOpen(); } + + void AddFile(const FileDescription& description, const void* buffer); + void AddFile(const std::filesystem::path& fileName, const char* inArchiveName); + }; +} \ No newline at end of file diff --git a/openVulkanoCpp/Math/CRC32.hpp b/openVulkanoCpp/Math/CRC32.hpp new file mode 100644 index 0000000..82bc954 --- /dev/null +++ b/openVulkanoCpp/Math/CRC32.hpp @@ -0,0 +1,53 @@ +/* + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +#pragma once + +#include +#include + +class CRC32 +{ + std::array m_table; + uint32_t m_currentValue = 0; + +public: + CRC32() + { + uint32_t polynomial = 0xEDB88320; + for (uint32_t i = 0; i < m_table.size(); i++) + { + uint32_t c = i; + for (size_t j = 0; j < 8; j++) + { + if (c & 1) + { + c = polynomial ^ (c >> 1); + } + else + { + c >>= 1; + } + } + m_table[i] = c; + } + } + + void Update(size_t len, const uint8_t* buf) + { + uint32_t c = m_currentValue ^ 0xFFFFFFFF; + for (size_t i = 0; i < len; ++i) + { + c = m_table[(c ^ buf[i]) & 0xFF] ^ (c >> 8); + } + m_currentValue = c ^ 0xFFFFFFFF; + } + + uint32_t GetValue() const + { + return m_currentValue; + } +}; \ No newline at end of file diff --git a/openVulkanoCpp/Scene/Geometry.cpp b/openVulkanoCpp/Scene/Geometry.cpp index a05f0b2..4fa4c9f 100644 --- a/openVulkanoCpp/Scene/Geometry.cpp +++ b/openVulkanoCpp/Scene/Geometry.cpp @@ -153,4 +153,25 @@ namespace OpenVulkano::Scene vertices = nullptr; indices = nullptr; } + + uint32_t Geometry::GetIndex(uint32_t index) const + { + uint32_t result = 0; + + if (index >= indexCount) [[unlikely]] + throw std::out_of_range("Index is out of range"); + + if (indexType == OpenVulkano::Scene::VertexIndexType::UINT16) + { + uint16_t* indices = GetIndices16(); + result = indices[index]; + } + else if (indexType == OpenVulkano::Scene::VertexIndexType::UINT32) + { + uint32_t* indices = GetIndices32(); + result = indices[index]; + } + + return result; + } } diff --git a/openVulkanoCpp/Scene/Geometry.hpp b/openVulkanoCpp/Scene/Geometry.hpp index c5c1159..5522ac5 100644 --- a/openVulkanoCpp/Scene/Geometry.hpp +++ b/openVulkanoCpp/Scene/Geometry.hpp @@ -57,6 +57,7 @@ namespace OpenVulkano Vertex* GetVertices() const { return vertices; } void* GetIndices() const { return indices; } + uint32_t GetIndex(uint32_t index) const; uint16_t* GetIndices16() const { return static_cast(indices); } uint32_t* GetIndices32() const { return static_cast(indices); } uint32_t GetIndexCount() const { return indexCount; } diff --git a/openVulkanoCpp/Scene/MeshWriter.cpp b/openVulkanoCpp/Scene/MeshWriter.cpp index be6326f..7ee4ab5 100644 --- a/openVulkanoCpp/Scene/MeshWriter.cpp +++ b/openVulkanoCpp/Scene/MeshWriter.cpp @@ -5,39 +5,16 @@ */ #include "MeshWriter.hpp" +#include "IO/MemMappedFile.hpp" #include "Scene/Geometry.hpp" #include "Scene/Vertex.hpp" +#include "Scene/UsdEncoder.hpp" +#include "Scene/ObjEncoder.hpp" +#include "IO/Archive/ArchiveWriter.hpp" +#include "IO/Archive/ZipWriter.hpp" + #include #include -#include -#include -#include -#include - -namespace -{ - uint32_t GetIndexFromGeometry(OpenVulkano::Scene::Geometry* geometry, int index) - { - uint32_t result = 0; - - if (geometry->indexType == OpenVulkano::Scene::VertexIndexType::UINT16) - { - uint16_t *indices = static_cast(geometry->indices); - result = indices[index]; - } - else if (geometry->indexType == OpenVulkano::Scene::VertexIndexType::UINT32) - { - uint32_t *indices = static_cast(geometry->indices); - result = indices[index]; - } - else - { - throw std::runtime_error("Invalid geometry index type"); - } - - return result; - } -} namespace OpenVulkano::Scene { @@ -45,110 +22,63 @@ namespace OpenVulkano::Scene { std::ofstream file(filePath); - if (!file.is_open()) + if (!file.is_open()) [[unlikely]] throw std::runtime_error("Failed to open file '" + filePath + "' for writing!"); - // Vertices - for (int i = 0; i < geometry->vertexCount; ++i) - { - const OpenVulkano::Vertex& v = geometry->vertices[i]; - std::string line; - line = fmt::format("v {} {} {}\n", v.position.x, v.position.y, v.position.z); - file << line; - } - - // Normals - for (int i = 0; i < geometry->vertexCount; ++i) - { - const OpenVulkano::Vertex& v = geometry->vertices[i]; - std::string line; - line = fmt::format("vn {} {} {}\n", v.normal.x, v.normal.y, v.normal.z); - file << line; - } - - // TexCoords - for (int i = 0; i < geometry->vertexCount; ++i) - { - const OpenVulkano::Vertex& v = geometry->vertices[i]; - std::string line; - line = fmt::format("vt {} {}\n", v.textureCoordinates.x, v.textureCoordinates.y); - file << line; - } - - // Indices - for (int i = 0; i < geometry->indexCount; i += 3) - { - uint32_t i0 = GetIndexFromGeometry(geometry, i + 0) + 1; - uint32_t i1 = GetIndexFromGeometry(geometry, i + 1) + 1; - uint32_t i2 = GetIndexFromGeometry(geometry, i + 2) + 1; - std::string line = fmt::format("f {0}/{0}/{0} {1}/{1}/{1} {2}/{2}/{2}\n", i0, i1, i2); - file << line; - } - + WriteObjContents(geometry, "", file); file.close(); } void MeshWriter::WriteAsUSD(Geometry* geometry, const std::string& filePath) { - tinyusdz::Stage stage; - tinyusdz::Xform xform; - tinyusdz::GeomMesh mesh; - mesh.name = "TheMesh"; - - std::vector pts(geometry->vertexCount); - std::vector indices(geometry->indexCount); - tinyusdz::Attribute uvAttr; - std::vector uvs(geometry->vertexCount); - - for (uint32_t i = 0; i < geometry->vertexCount; ++i) - { - const Vertex& v = geometry->vertices[i]; - pts[i].x = v.position.x; - pts[i].y = v.position.y; - pts[i].z = v.position.z; - uvs[i] = { v.textureCoordinates.x, v.textureCoordinates.y }; - } - - mesh.points.set_value(pts); - uvAttr.set_value(uvs); - - std::vector counts(geometry->indexCount / 3, 3); // NOTE(vb): The value 3 is kind of arbitrary, but this array must be in the mesh! - mesh.faceVertexCounts.set_value(counts); - - for (uint32_t i = 0; i < geometry->indexCount; ++i) - { - uint32_t index = GetIndexFromGeometry(geometry, i); - indices[i] = index; - } - mesh.faceVertexIndices.set_value(indices); - - uvAttr.metas().interpolation = tinyusdz::Interpolation::FaceVarying; - tinyusdz::Property uvProp(uvAttr); - mesh.props.emplace("primvars:UVMap", uvProp); - - tinyusdz::Prim xformPrim(xform); - tinyusdz::Prim meshPrim(mesh); - std::string err; - if (!xformPrim.add_child(std::move(meshPrim), true, &err)) - { - throw std::runtime_error("Failed to construct scene: " + err); - } - if (!stage.add_root_prim(std::move(xformPrim))) - { - throw std::runtime_error("Failed to add prim to stage root: " + stage.get_error()); - } - stage.metas().defaultPrim = tinyusdz::value::token(xformPrim.element_name()); - stage.metas().comment = "Generated by OpenVulkanoCpp"; - if (!stage.commit()) - { - throw std::runtime_error("Failed to commit stage: " + stage.get_error()); - } - std::ofstream file(filePath); - if (!file.is_open()) + if (!file.is_open()) [[unlikely]] throw std::runtime_error("Failed to open file '" + filePath + "' for writing!"); - std::string scene = to_string(stage); - file << scene << "\n"; + WriteUsdContents(file, geometry); file.close(); } + + void MeshWriter::WriteObjAsZip(Geometry* geometry, const std::string& texturePath, const std::string& zipPath) + { + OpenVulkano::ArchiveWriter zipWriter(zipPath.c_str()); + + { + std::stringstream objContents; + WriteObjContents(geometry, DEFAULT_OBJ_MATERIAL_NAME, objContents); + std::string objContentsStr = objContents.str(); + FileDescription objDesc = FileDescription::MakeDescriptionForFile("model.obj", objContentsStr.size()); + zipWriter.AddFile(objDesc, objContentsStr.data()); + } + { + FileDescription mtlDesc = FileDescription::MakeDescriptionForFile("material.mtl", DEFAULT_OBJ_MATERIAL_CONTENTS.size()); + zipWriter.AddFile(mtlDesc, DEFAULT_OBJ_MATERIAL_CONTENTS.data()); + } + + if (!texturePath.empty() && std::filesystem::exists(texturePath)) + { + MemMappedFile textureFile(texturePath); + FileDescription texDesc = FileDescription::MakeDescriptionForFile("texture.png", textureFile.Size()); + zipWriter.AddFile(texDesc, textureFile.Data()); + } + } + + void MeshWriter::WriteAsUSDZ(Geometry* geometry, const std::string& texturePath, const std::string& usdzPath) + { + OpenVulkano::ZipWriter zipWriter(usdzPath, true); + + { + std::stringstream usdFile; + WriteUsdContents(usdFile, geometry); + std::string usdFileStr = usdFile.str(); + FileDescription usdDesc = FileDescription::MakeDescriptionForFile("geometry.usda", usdFileStr.size()); + zipWriter.AddFile(usdDesc, usdFileStr.data()); + } + + if (!texturePath.empty() && std::filesystem::exists(texturePath)) + { + MemMappedFile textureFile(texturePath); + FileDescription texDesc = FileDescription::MakeDescriptionForFile("texture.png", textureFile.Size()); + zipWriter.AddFile(texDesc, textureFile.Data()); + } + } } \ No newline at end of file diff --git a/openVulkanoCpp/Scene/MeshWriter.hpp b/openVulkanoCpp/Scene/MeshWriter.hpp index 940cc97..c484bee 100644 --- a/openVulkanoCpp/Scene/MeshWriter.hpp +++ b/openVulkanoCpp/Scene/MeshWriter.hpp @@ -16,5 +16,7 @@ namespace OpenVulkano::Scene public: static void WriteAsOBJ(Geometry* geometry, const std::string& filePath); static void WriteAsUSD(Geometry* geometry, const std::string& filePath); + static void WriteObjAsZip(Geometry* geometry, const std::string& texturePath, const std::string& zipPath); + static void WriteAsUSDZ(Geometry* geometry, const std::string& texturePath, const std::string& usdzPath); }; } \ No newline at end of file diff --git a/openVulkanoCpp/Scene/ObjEncoder.hpp b/openVulkanoCpp/Scene/ObjEncoder.hpp new file mode 100644 index 0000000..f69eced --- /dev/null +++ b/openVulkanoCpp/Scene/ObjEncoder.hpp @@ -0,0 +1,69 @@ +/* + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ +#pragma once + +#include "Scene/Geometry.hpp" + +#include +#include +#include +#include + +namespace OpenVulkano::Scene +{ + static constexpr std::string_view DEFAULT_OBJ_MATERIAL_NAME = "Material0"; + static constexpr std::string_view DEFAULT_OBJ_MATERIAL_CONTENTS = R"(newmtl Material0 +Ka 1.000 1.000 1.000 +Kd 1.000 1.000 1.000 +Ks 0.000 0.000 0.000 +map_Ka texture.png +map_Kd texture.png +)"; + + void WriteObjContents(Geometry* geometry, const std::string_view& materialName, std::ostream& objContent) + { + objContent << "# OBJ file generated by OpenVulkanoCpp\n"; + + if (materialName.size() != 0) + { + std::string_view content = "mtllib material.mtl\n"; + objContent.write(content.data(), content.size()); + + content = "usemtl "; + objContent.write(content.data(), content.size()); + + objContent.write(materialName.data(), materialName.size()); + objContent.write("\n", 1); + } + + for (int i = 0; i < geometry->vertexCount; ++i) + { + const auto& v = geometry->vertices[i]; + const std::string content = fmt::format("v {} {} {}\n", v.position.x, v.position.y, v.position.z); + objContent.write(content.data(), content.size()); + } + for (int i = 0; i < geometry->vertexCount; ++i) + { + const auto& v = geometry->vertices[i]; + const std::string content = fmt::format("vn {} {} {}\n", v.normal.x, v.normal.y, v.normal.z); + objContent.write(content.data(), content.size()); + } + for (int i = 0; i < geometry->vertexCount; ++i) + { + const auto& v = geometry->vertices[i]; + const std::string content = fmt::format("vt {} {}\n", v.textureCoordinates.x, v.textureCoordinates.y); + objContent.write(content.data(), content.size()); + } + for (int i = 0; i < geometry->indexCount; i += 3) + { + uint32_t i0 = geometry->GetIndex(i + 0) + 1; + uint32_t i1 = geometry->GetIndex(i + 1) + 1; + uint32_t i2 = geometry->GetIndex(i + 2) + 1; + const std::string content = fmt::format("f {0}/{0}/{0} {1}/{1}/{1} {2}/{2}/{2}\n", i0, i1, i2); + objContent.write(content.data(), content.size()); + } + } +} \ No newline at end of file diff --git a/openVulkanoCpp/Scene/UsdEncoder.hpp b/openVulkanoCpp/Scene/UsdEncoder.hpp new file mode 100644 index 0000000..40d4632 --- /dev/null +++ b/openVulkanoCpp/Scene/UsdEncoder.hpp @@ -0,0 +1,155 @@ +/* + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ +#pragma once + +#include "Scene/Geometry.hpp" + +#include + +namespace OpenVulkano::Scene +{ + void WriteUsdContents(std::ostream& output, OpenVulkano::Scene::Geometry* geometry) + { + output << R"(#usda 1.0 +( + defaultPrim = "root" + doc = "Exported from OpenVulkano" + metersPerUnit = 1 + upAxis = "Y" +) + +def Xform "root" ( + customData = { + dictionary Blender = { + bool generated = 1 + } + } +) +{ + def Xform "model" + { + custom string userProperties:blender:object_name = "model" + float3 xformOp:rotateXYZ = (0, -0, 0) + float3 xformOp:scale = (1, 1, 1) + double3 xformOp:translate = (0, 0, 0) + uniform token[] xformOpOrder = ["xformOp:translate", "xformOp:rotateXYZ", "xformOp:scale"] + + def Mesh "model" ( + active = true + prepend apiSchemas = ["MaterialBindingAPI"] + ) + { + uniform bool doubleSided = 1 + float3[] extent = [(-0.5, -0.5, 0), (0.5, 0.5, 0)] + int[] faceVertexCounts = [)"; + + for (size_t i = 0; i < geometry->indexCount; ++i) + { + if ((i + 1) % 3 == 0) + { + if (i > 2) output << ", "; + output << "3"; + } + } + + output << R"(] + int[] faceVertexIndices = [)"; + + for (size_t i = 0; i < geometry->indexCount; ++i) + { + if (i > 0) output << ", "; + output << geometry->GetIndex(i); + } + + output << R"(] + rel material:binding = + normal3f[] normals = [)"; + + output << std::fixed << std::setprecision(6); + for (size_t i = 0; i < geometry->vertexCount; ++i) + { + const auto& v = geometry->vertices[i]; + if (i > 0) output << ", "; + output << "(" << v.normal.x << ", " << v.normal.y << ", " << v.normal.z << ")"; + } + + output << R"(] ( + interpolation = "faceVarying" + ) + point3f[] points = [)"; + + for (size_t i = 0; i < geometry->vertexCount; ++i) + { + const auto& v = geometry->vertices[i]; + if (i > 0) output << ", "; + output << "(" << v.position.x << ", " << v.position.y << ", " << v.position.z << ")"; + } + + output << R"(] + texCoord2f[] primvars:st = [)"; + + output << std::fixed << std::setprecision(6); + + for (size_t i = 0; i < geometry->indexCount; ++i) + { + const size_t vertexIndex = geometry->GetIndex(i); + const auto& v = geometry->vertices[vertexIndex]; + if (i > 0) output << ", "; + output << "(" << v.textureCoordinates.x << ", " << v.textureCoordinates.y << ")"; + } + + output << R"(] ( + interpolation = "faceVarying" + ) + uniform token subdivisionScheme = "none" + custom string userProperties:blender:data_name = "model" + } + } + + def Scope "_materials" + { + def Material "Material0" + { + token outputs:surface.connect = + custom string userProperties:blender:data_name = "Material0" + + def Shader "Principled_BSDF" + { + uniform token info:id = "UsdPreviewSurface" + float inputs:clearcoat = 0 + float inputs:clearcoatRoughness = 0.03 + color3f inputs:diffuseColor.connect = + float inputs:ior = 1.5 + float inputs:metallic = 0 + float inputs:opacity = 1 + float inputs:roughness = 1 + float inputs:specular = 0 + token outputs:surface + } + + def Shader "Image_Texture" + { + uniform token info:id = "UsdUVTexture" + asset inputs:file = @./texture.png@ + token inputs:sourceColorSpace = "sRGB" + float2 inputs:st.connect = + token inputs:wrapS = "repeat" + token inputs:wrapT = "repeat" + float3 outputs:rgb + } + + def Shader "uvmap" + { + uniform token info:id = "UsdPrimvarReader_float2" + string inputs:varname = "st" + float2 outputs:result + } + } + } +} +)"; + } +} \ No newline at end of file diff --git a/tests/IO/Archive/ZipWriterTest.cpp b/tests/IO/Archive/ZipWriterTest.cpp new file mode 100644 index 0000000..6b70d21 --- /dev/null +++ b/tests/IO/Archive/ZipWriterTest.cpp @@ -0,0 +1,113 @@ +/* + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +#include + +#include "Base/Utils.hpp" +#include "IO/Archive/ZipWriter.hpp" +#include "IO/AppFolders.hpp" + +#include +#include +#include + +using namespace OpenVulkano; + +TEST_CASE("Empty zip file", "[ZipWriter]") +{ + const auto emptyZipPath = AppFolders::GetAppTempDir() / "empty.zip"; + { + ZipWriter writer(emptyZipPath); + } + auto mem = Utils::ReadFile(emptyZipPath); + const int expectSize = 22; + Array expect = {0x50, 0x4b, 0x05, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + + CHECK(mem.Size() == expectSize); + CHECK(mem == expect); + + std::filesystem::remove(emptyZipPath); +} + +TEST_CASE("Zip with one file(AAA.txt that has 'AAA')", "[ZipWriter]") +{ + const auto oneFileZipPath = AppFolders::GetAppTempDir() / "one_file.zip"; + + { + ZipWriter writer(oneFileZipPath); + + FileDescription desc = FileDescription::MakeDescriptionForFile("AAA.txt", 3); + desc.modTime = {}; + desc.createTime = {}; + char buffer[] = {'A', 'A', 'A'}; + + writer.AddFile(desc, buffer); + } + + auto mem = Utils::ReadFile(oneFileZipPath); + + const int expectSize = 151; + Array expect = + { + 0x50, 0x4b, 0x03, 0x04, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0xec, 0xa7, 0x31, 0xa0, 0x66, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x07, 0x00, + 0x00, 0x00, 0x41, 0x41, 0x41, 0x2e, 0x74, 0x78, 0x74, 0x41, 0x41, 0x41, 0x50, 0x4b, 0x01, 0x02, 0x1f, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0xec, + 0xa7, 0x31, 0xa0, 0x66, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x07, 0x00, 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x41, 0x41, 0x41, 0x2e, 0x74, 0x78, 0x74, 0x0a, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x18, 0x00, 0x00, 0x80, 0x3e, 0xd5, 0xde, 0xb1, 0x9d, + 0x01, 0x00, 0x80, 0x3e, 0xd5, 0xde, 0xb1, 0x9d, 0x01, 0x00, 0x80, 0x3e, 0xd5, 0xde, 0xb1, 0x9d, 0x01, 0x50, 0x4b, 0x05, 0x06, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00 + }; + + CHECK(mem.Size() == expectSize); + CHECK(mem == expect); + + std::filesystem::remove(oneFileZipPath); +} + + +TEST_CASE("Zip with two files(AAA.txt that has 'AAA', BBB.bin that has 'BBB')", "[ZipWriter]") +{ + const auto twoFilesZipPath = AppFolders::GetAppTempDir() / "two_files.zip"; + { + ZipWriter writer(twoFilesZipPath); + + FileDescription aaa = FileDescription::MakeDescriptionForFile("AAA.txt", 3); + aaa.modTime = {}; + aaa.createTime = {}; + char aaaBuffer[] = {'A', 'A', 'A'}; + + FileDescription bbb = FileDescription::MakeDescriptionForFile("BBB.bin", 3); + bbb.modTime = {}; + bbb.createTime = {}; + char bbbBuffer[] = {'B', 'B', 'B'}; + + writer.AddFile(aaa, aaaBuffer); + writer.AddFile(bbb, bbbBuffer); + } + + auto mem = Utils::ReadFile(twoFilesZipPath); + + const int expectSize = 280; + Array expect = + { + 0x50, 0x4b, 0x03, 0x04, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0xec, 0xa7, 0x31, 0xa0, 0x66, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x07, 0x00, + 0x00, 0x00, 0x41, 0x41, 0x41, 0x2e, 0x74, 0x78, 0x74, 0x41, 0x41, 0x41, 0x50, 0x4b, 0x03, 0x04, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0xec, 0x87, 0x8d, + 0xc2, 0xd6, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x42, 0x42, 0x42, 0x2e, 0x62, 0x69, 0x6e, 0x42, 0x42, 0x42, 0x50, 0x4b, 0x01, 0x02, + 0x1f, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0xec, 0xa7, 0x31, 0xa0, 0x66, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x07, 0x00, 0x24, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x41, 0x41, 0x41, 0x2e, 0x74, 0x78, 0x74, 0x0a, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x01, 0x00, 0x18, 0x00, 0x00, 0x80, 0x3e, 0xd5, 0xde, 0xb1, 0x9d, 0x01, 0x00, 0x80, 0x3e, 0xd5, 0xde, 0xb1, 0x9d, 0x01, 0x00, 0x80, 0x3e, 0xd5, 0xde, 0xb1, 0x9d, + 0x01, 0x50, 0x4b, 0x01, 0x02, 0x1f, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0xec, 0x87, 0x8d, 0xc2, 0xd6, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, + 0x00, 0x07, 0x00, 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x42, 0x42, 0x42, 0x2e, 0x62, 0x69, 0x6e, 0x0a, 0x00, + 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x18, 0x00, 0x00, 0x80, 0x3e, 0xd5, 0xde, 0xb1, 0x9d, 0x01, 0x00, 0x80, 0x3e, 0xd5, 0xde, 0xb1, 0x9d, 0x01, 0x00, 0x80, + 0x3e, 0xd5, 0xde, 0xb1, 0x9d, 0x01, 0x50, 0x4b, 0x05, 0x06, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00 + }; + + CHECK(mem.Size() == expectSize); + CHECK(mem == expect); + + std::filesystem::remove(twoFilesZipPath); +} + +