From 6dff63071c37bb9790fc42c8ac8183ef0e4d2e46 Mon Sep 17 00:00:00 2001 From: jan Date: Mon, 16 Aug 2021 12:02:09 +0200 Subject: [PATCH] PLYLoader --- Pathtracer.vcxproj | 2 + Pathtracer.vcxproj.filters | 6 + Src/Assets/AssetManager.cpp | 4 +- Src/Assets/PLYLoader.cpp | 380 ++++++++++++++++++++++++++++++++++++ Src/Assets/PLYLoader.h | 6 + Src/Pathtracer/Scene.cpp | 2 +- Src/Util/Parser.h | 12 ++ 7 files changed, 410 insertions(+), 2 deletions(-) create mode 100644 Src/Assets/PLYLoader.cpp create mode 100644 Src/Assets/PLYLoader.h diff --git a/Pathtracer.vcxproj b/Pathtracer.vcxproj index 3299268a..3ffdfd86 100644 --- a/Pathtracer.vcxproj +++ b/Pathtracer.vcxproj @@ -204,6 +204,7 @@ + @@ -247,6 +248,7 @@ + diff --git a/Pathtracer.vcxproj.filters b/Pathtracer.vcxproj.filters index 8d1d6c4a..1f183664 100644 --- a/Pathtracer.vcxproj.filters +++ b/Pathtracer.vcxproj.filters @@ -109,6 +109,9 @@ Math + + Assets + @@ -313,5 +316,8 @@ Util + + Assets + \ No newline at end of file diff --git a/Src/Assets/AssetManager.cpp b/Src/Assets/AssetManager.cpp index 2d88ab91..17ea464d 100644 --- a/Src/Assets/AssetManager.cpp +++ b/Src/Assets/AssetManager.cpp @@ -45,6 +45,8 @@ void AssetManager::init() { thread_pool.init(); } +#include "PLYLoader.h" + MeshDataHandle AssetManager::add_mesh_data(const char * filename) { MeshDataHandle & mesh_data_handle = mesh_data_cache[filename]; @@ -56,7 +58,7 @@ MeshDataHandle AssetManager::add_mesh_data(const char * filename) { bool bvh_loaded = BVHLoader::try_to_load(filename, mesh_data, bvh); if (!bvh_loaded) { // Unable to load disk cached BVH, load model from source and construct BVH - OBJLoader::load(filename, mesh_data.triangles, mesh_data.triangle_count); + PLYLoader::load(filename, mesh_data.triangles, mesh_data.triangle_count); bvh = build_bvh(mesh_data.triangles, mesh_data.triangle_count); BVHLoader::save(filename, mesh_data, bvh); diff --git a/Src/Assets/PLYLoader.cpp b/Src/Assets/PLYLoader.cpp new file mode 100644 index 00000000..735972f8 --- /dev/null +++ b/Src/Assets/PLYLoader.cpp @@ -0,0 +1,380 @@ +#include "PLYLoader.h" + +#include "Util/Array.h" +#include "Util/Parser.h" +#include "Util/StringView.h" + +enum struct Format { + ASCII, + BINARY_LITTLE_ENDIAN, + BINARY_BIG_ENDIAN +}; + +struct Property { + struct Type { + enum struct Kind { + INT8, + INT16, + INT32, + UINT8, + UINT16, + UINT32, + FLOAT32, + FLOAT64, + LIST + } kind; + union { + struct { + Kind size_type_kind; + Kind list_type_kind; + } list; + struct { + StringView name; + } custom; + }; + } type; + + enum struct Kind { + X, + Y, + Z, + NX, + NY, + NZ, + U, + V, + VERTEX_INDEX + } kind; +}; + +struct Element { + struct Type { + enum struct Kind { + VERTEX, + FACE + } kind; + + StringView custom_name; + } type; + + int count; + + static constexpr int MAX_PROPERTIES = 16; + + Property properties[MAX_PROPERTIES]; + int property_count; +}; + +static Property::Type parse_property_type(Parser & parser) { + parser.skip_whitespace(); + + Property::Type type = { }; + + if (parser.match("int8") || parser.match("char")) { + type.kind = Property::Type::Kind::INT8; + } else if (parser.match("int16") || parser.match("short")) { + type.kind = Property::Type::Kind::INT16; + } else if (parser.match("int32") || parser.match("int")) { + type.kind = Property::Type::Kind::INT32; + } else if (parser.match("uint8") || parser.match("uchar")) { + type.kind = Property::Type::Kind::UINT8; + } else if (parser.match("uint16") || parser.match("ushort")) { + type.kind = Property::Type::Kind::UINT16; + } else if (parser.match("uint32") || parser.match("uint")) { + type.kind = Property::Type::Kind::UINT32; + } else if (parser.match("float32") || parser.match("float")) { + type.kind = Property::Type::Kind::FLOAT32; + } else if (parser.match("float64") || parser.match("double")) { + type.kind = Property::Type::Kind::FLOAT64; + } else if (parser.match("list")) { + type.kind = Property::Type::Kind::LIST; + type.list.size_type_kind = parse_property_type(parser).kind; + type.list.list_type_kind = parse_property_type(parser).kind; + } else { + ERROR(parser.location, "Invalid type!\n"); + } + + return type; +} + +static StringView parse_name(Parser & parser) { + parser.skip_whitespace(); + + const char * start = parser.cur; + while (!parser.reached_end() && !is_whitespace(*parser.cur) && !is_newline(*parser.cur)) { + parser.advance(); + } + + return StringView { start, parser.cur }; +} + +template +static T parse_value(Parser & parser, Format format) { + const char * start = parser.cur; + + for (int i = 0; i < sizeof(T); i++) { + parser.advance(); + } + + T value = { }; + + // NOTE: Assumes machine is little endian! + switch (format) { + case Format::BINARY_LITTLE_ENDIAN: memcpy(&value, start, sizeof(T)); break; + case Format::BINARY_BIG_ENDIAN: { + char * dst = reinterpret_cast(&value); + const char * src = start; + for (int i = 0; i < sizeof(T) / 2; i++) { + dst[i] = src[sizeof(T) - 1 - i]; + } + break; + } + default: abort(); + } + + return value; +} + +template +static T parse_property_value(Parser & parser, Property::Type::Kind kind, Format format) { + if (format == Format::ASCII) { + parser.skip_whitespace(); + if (kind == Property::Type::Kind::FLOAT32 || kind == Property::Type::Kind::FLOAT64) { + return parser.parse_float(); + } else { + return parser.parse_int(); + } + } else { + // Parse as binary data + switch (kind) { + case Property::Type::Kind::INT8: return parse_value (parser, format); break; + case Property::Type::Kind::INT16: return parse_value (parser, format); break; + case Property::Type::Kind::INT32: return parse_value (parser, format); break; + case Property::Type::Kind::UINT8: return parse_value (parser, format); break; + case Property::Type::Kind::UINT16: return parse_value(parser, format); break; + case Property::Type::Kind::UINT32: return parse_value(parser, format); break; + case Property::Type::Kind::FLOAT32: return parse_value (parser, format); break; + case Property::Type::Kind::FLOAT64: return parse_value (parser, format); break; + + default: ERROR(parser.location, "Invalid property type!\n"); break; + } + } +} + +void PLYLoader::load(const char * filename, Triangle *& triangles, int & triangle_count) { + int file_length; + const char * file = Util::file_read(filename, file_length); + + SourceLocation location = { }; + location.file = filename; + location.line = 1; + location.col = 1; + + Parser parser; + parser.init(file, file + file_length, location); + + parser.expect("ply\n"); + parser.expect("format"); + parser.skip_whitespace(); + + Format format; + + if (parser.match("ascii")) { + format = Format::ASCII; + } else if (parser.match("binary_little_endian")) { + format = Format::BINARY_LITTLE_ENDIAN; + } else if (parser.match("binary_big_endian")) { + format = Format::BINARY_BIG_ENDIAN; + } else { + ERROR(parser.location, "Invalid PLY format!\n"); + } + parser.skip_whitespace(); + + int version_major = parser.parse_int(); + parser.expect('.'); + int version_minor = parser.parse_int(); + parser.skip_whitespace_or_newline(); + + if (version_major != 1 || version_minor != 0) { + WARNING(parser.location, "PLY format version is not 1.0!\n"); + } + + Array elements; + + while (!parser.match("end_header")) { + if (parser.match("comment")) { + while (!is_newline(*parser.cur)) parser.advance(); + } else if (parser.match("element")) { + parser.skip_whitespace(); + + Element element = { }; + + if (parser.match("vertex ")) { + element.type.kind = Element::Type::Kind::VERTEX; + } else if (parser.match("face ")) { + element.type.kind = Element::Type::Kind::FACE; + } else { + ERROR(parser.location, "Unsupported element type!\n"); + } + parser.skip_whitespace(); + + element.count = parser.parse_int(); + elements.push_back(element); + } else if (parser.match("property")) { + parser.skip_whitespace(); + + if (elements.size() == 0) { + ERROR(parser.location, "Property defined without element!\n"); + } + Element & element = elements.back(); + + if (element.property_count == Element::MAX_PROPERTIES) { + ERROR(parser.location, "Maximum number of properties (%i) exceeded!\n", Element::MAX_PROPERTIES); + } + Property & property = element.properties[element.property_count++]; + + property.type = parse_property_type(parser); + + StringView name = parse_name(parser); + if (name == "x") { + property.kind = Property::Kind::X; + } else if (name == "y") { + property.kind = Property::Kind::Y; + } else if (name == "z") { + property.kind = Property::Kind::Z; + } else if (name == "nx") { + property.kind = Property::Kind::NX; + } else if (name == "ny") { + property.kind = Property::Kind::NY; + } else if (name == "nz") { + property.kind = Property::Kind::NZ; + } else if (name == "u" || name == "s") { + property.kind = Property::Kind::U; + } else if (name == "v" || name == "t") { + property.kind = Property::Kind::V; + } else if (name == "vertex_index" || name == "vertex_indices") { + property.kind = Property::Kind::VERTEX_INDEX; + } else { + ERROR(parser.location, "Unknown property '%.*s'!\n", unsigned(name.length()), name.c_str()); + } + } + + parser.parse_newline(); + } + parser.parse_newline(); + + Array positions; + Array tex_coords; + Array normals; + + Array tris; + + for (int e = 0; e < elements.size(); e++) { + const Element & element = elements[e]; + + switch (element.type.kind) { + case Element::Type::Kind::VERTEX: { + for (int i = 0; i < element.count; i++) { + Vector3 position = Vector3(0.0f); + Vector2 tex_coord = Vector2(0.0f); + Vector3 normal = Vector3(0.0f); + + for (int p = 0; p < element.property_count; p++) { + const Property & property = element.properties[p]; + + float value = parse_property_value(parser, property.type.kind, format); + + switch (property.kind) { + case Property::Kind::X: position.x = value; break; + case Property::Kind::Y: position.y = value; break; + case Property::Kind::Z: position.z = value; break; + case Property::Kind::NX: normal.x = value; break; + case Property::Kind::NY: normal.y = value; break; + case Property::Kind::NZ: normal.z = value; break; + case Property::Kind::U: tex_coord.x = value; break; + case Property::Kind::V: tex_coord.y = value; break; + } + } + + positions .push_back(position); + tex_coords.push_back(tex_coord); + normals .push_back(normal); + + if (format == Format::ASCII) parser.parse_newline(); + } + + break; + } + + case Element::Type::Kind::FACE: { + if (element.property_count > 1) { + WARNING(parser.location, "Warning: Face has more than one property!\n"); + } + for (int i = 0; i < element.count; i++) { + for (int p = 0; p < element.property_count; p++) { + const Property & property = element.properties[p]; + + if (property.kind != Property::Kind::VERTEX_INDEX) { + ERROR(parser.location, "Face property should be vertex_index!\n"); + } + if (property.type.kind != Property::Type::Kind::LIST) { + ERROR(parser.location, "Face property should have list type!\n"); + } + + size_t size = parse_property_value(parser, property.type.list.size_type_kind, format); + + if (size <= 2) { + ERROR(parser.location, "A Triangle needs at least 3 indices!\n"); + } + + size_t elem_0 = parse_property_value(parser, property.type.list.list_type_kind, format); + size_t elem_1 = parse_property_value(parser, property.type.list.list_type_kind, format); + + Vector3 pos[3] = { positions [elem_0], positions [elem_1] }; + Vector2 tex[3] = { tex_coords[elem_0], tex_coords[elem_1] }; + Vector3 nor[3] = { normals [elem_0], normals [elem_1] }; + + for (size_t i = 2; i < size; i++) { + size_t elem_2 = parse_property_value(parser, property.type.list.list_type_kind, format); + pos[2] = positions [elem_2]; + tex[2] = tex_coords[elem_2]; + nor[2] = normals [elem_2]; + + Triangle triangle = { }; + triangle.position_0 = pos[0]; + triangle.position_1 = pos[1]; + triangle.position_2 = pos[2]; + triangle.tex_coord_0 = tex[0]; + triangle.tex_coord_1 = tex[1]; + triangle.tex_coord_2 = tex[2]; + triangle.normal_0 = nor[0]; + triangle.normal_1 = nor[1]; + triangle.normal_2 = nor[2]; + triangle.calc_aabb(); + + tris.push_back(triangle); + + pos[1] = pos[2]; + tex[1] = tex[2]; + nor[1] = nor[2]; + } + } + + if (format == Format::ASCII && !parser.reached_end()) parser.parse_newline(); + } + + break; + } + + default: abort(); + } + } + + assert(parser.reached_end()); + + delete [] file; + + triangle_count = tris.size(); + triangles = new Triangle[triangle_count]; + memcpy(triangles, tris.data(), triangle_count * sizeof(Triangle)); +} diff --git a/Src/Assets/PLYLoader.h b/Src/Assets/PLYLoader.h new file mode 100644 index 00000000..aa107b43 --- /dev/null +++ b/Src/Assets/PLYLoader.h @@ -0,0 +1,6 @@ +#pragma once +#include "Pathtracer/Triangle.h" + +namespace PLYLoader { + void load(const char * filename, Triangle *& triangles, int & triangle_count); +} diff --git a/Src/Pathtracer/Scene.cpp b/Src/Pathtracer/Scene.cpp index aefdb99f..91c78c51 100644 --- a/Src/Pathtracer/Scene.cpp +++ b/Src/Pathtracer/Scene.cpp @@ -21,7 +21,7 @@ void Scene::init(const char * scene_name, const char * sky_name) { const char * file_extension = Util::file_get_extension(scene_name); - if (strcmp(file_extension, "obj") == 0) { + if (strcmp(file_extension, "obj") == 0 || strcmp(file_extension, "ply") == 0) { add_mesh(scene_name, asset_manager.add_mesh_data(scene_name)); } else if (strcmp(file_extension, "xml") == 0) { MitsubaLoader::load(scene_name, *this); diff --git a/Src/Util/Parser.h b/Src/Util/Parser.h index 920d6a1b..e54ad2a7 100644 --- a/Src/Util/Parser.h +++ b/Src/Util/Parser.h @@ -97,6 +97,13 @@ struct Parser { } advance(); } + + template + void expect(const char (& target)[N]) { + for (int i = 0; i < N - 1; i++) { + expect(target[i]); + } + } float parse_float() { bool sign = false; @@ -154,4 +161,9 @@ struct Parser { return sign ? -value : value; } + + void parse_newline() { + match('\r'); + expect('\n'); + } };