From 49047a5e037eef4023fcc2f555f6c699a67e6d6e Mon Sep 17 00:00:00 2001 From: Tobias Frisch <tfrisch@uni-koblenz.de> Date: Mon, 19 Jul 2021 22:16:05 +0200 Subject: [PATCH] [#79] Asset-Loader refactoring mostly done Signed-off-by: Tobias Frisch <tfrisch@uni-koblenz.de> --- .../include/vkcv/asset/asset_loader.hpp | 204 +-- .../src/vkcv/asset/asset_loader.cpp | 1422 ++++++++--------- 2 files changed, 776 insertions(+), 850 deletions(-) diff --git a/modules/asset_loader/include/vkcv/asset/asset_loader.hpp b/modules/asset_loader/include/vkcv/asset/asset_loader.hpp index 04f0b39b..88021075 100644 --- a/modules/asset_loader/include/vkcv/asset/asset_loader.hpp +++ b/modules/asset_loader/include/vkcv/asset/asset_loader.hpp @@ -49,14 +49,24 @@ namespace vkcv::asset { * (TRIANGLES) if no mode is given in the file. */ enum class PrimitiveMode : uint8_t { - POINTS=0, LINES, LINELOOP, LINESTRIP, TRIANGLES, TRIANGLESTRIP, - TRIANGLEFAN + POINTS = 0, + LINES = 1, + LINELOOP = 2, + LINESTRIP = 3, + TRIANGLES = 4, + TRIANGLESTRIP = 5, + TRIANGLEFAN = 6 }; /** * The indices in the index buffer can be of different bit width. */ -enum class IndexType : uint8_t { UNDEFINED=0, UINT8=1, UINT16=2, UINT32=3 }; +enum class IndexType : uint8_t { + UNDEFINED=0, + UINT8=1, + UINT16=2, + UINT32=3 +}; /** * This struct defines a sampler for a texture object. All values here can @@ -67,25 +77,29 @@ enum class IndexType : uint8_t { UNDEFINED=0, UINT8=1, UINT16=2, UINT32=3 }; * Since glTF does not specify border sampling for more than two dimensions, * the addressModeW is hardcoded to a default: VK_SAMPLER_ADDRESS_MODE_REPEAT. */ -typedef struct { +struct Sampler { int minFilter, magFilter; int mipmapMode; float minLOD, maxLOD; int addressModeU, addressModeV, addressModeW; -} Sampler; +}; /** * This struct describes a loaded texture. * Note that textures are currently always loaded with 4 channels as RGBA, even * if the image has just RGB or is grayscale. */ -typedef struct { - int uri; // index into the URIs array of the Scene - int sampler; // index into the sampler array of the Scene - uint8_t channels; // number of channels - uint16_t w, h; // width and height of the texture - std::vector<uint8_t> data; // binary data of the decoded texture -} Texture; +struct Texture { + std::filesystem::path path; // file path of the encoded texture data + int sampler; // index into the sampler array of the Scene + + union { int width; int w; }; + union { int height; int h; }; + int channels; + + // binary data of the decoded texture + std::vector<char*> data; +}; /** * Flags for the bit-mask in the Material struct. To check if a material has a @@ -93,7 +107,11 @@ typedef struct { * the material struct and the enum. */ enum class PBRTextureTarget { - baseColor=1, metalRough=2, normal=4, occlusion=8, emissive=16 + baseColor=1, + metalRough=2, + normal=4, + occlusion=8, + emissive=16 }; /** @@ -112,10 +130,12 @@ enum class PBRTextureTarget { * The asset loader module only supports the PBR-MetallicRoughness model for * materials. */ -typedef struct { +struct Material { uint16_t textureMask; // bit mask with active texture targets + // Indices into the Scene.textures vector int baseColor, metalRough, normal, occlusion, emissive; + // Scaling factors for each texture target struct { float r, g, b, a; } baseColorFactor; float metallicFactor, roughnessFactor; @@ -128,12 +148,13 @@ typedef struct { * struct, its bit is set in the textureMask. You can use this function * to check that: * if (myMaterial.hasTexture(baseColor)) {...} + * * @param t The target to query for * @return Boolean to signal whether the texture target is active in * the material. */ - bool hasTexture(const PBRTextureTarget t) const; -} Material; + bool hasTexture(PBRTextureTarget target) const; +}; /* With these enums, 0 is reserved to signal uninitialized or invalid data. */ enum class PrimitiveType : uint32_t { @@ -157,21 +178,28 @@ enum class PrimitiveType : uint32_t { * avoid exposing that dependency, thus it is re-defined here. */ enum class ComponentType : uint16_t { - NONE = 0, INT8 = 5120, UINT8 = 5121, INT16 = 5122, UINT16 = 5123, - UINT32 = 5125, FLOAT32 = 5126 + NONE = 0, + INT8 = 5120, + UINT8 = 5121, + INT16 = 5122, + UINT16 = 5123, + UINT32 = 5125, + FLOAT32 = 5126 }; /** * This struct describes one vertex attribute of a vertex buffer. */ -typedef struct { +struct VertexAttribute { PrimitiveType type; // POSITION, NORMAL, ... + uint32_t offset; // offset in bytes uint32_t length; // length of ... in bytes uint32_t stride; // stride in bytes + ComponentType componentType; // eg. 5126 for float uint8_t componentCount; // eg. 3 for vec3 -} VertexAttribute; +}; /** * This struct represents one (possibly the only) part of a mesh. There is @@ -180,100 +208,96 @@ typedef struct { * by indexBuffer.data being empty. Each vertex buffer can have one or more * vertex attributes. */ -typedef struct { +struct VertexGroup { enum PrimitiveMode mode; // draw as points, lines or triangle? - size_t numIndices, numVertices; + size_t numIndices; + size_t numVertices; + struct { - int uri; // index into the URIs array of Scene enum IndexType type; // data type of the indices std::vector<uint8_t> data; // binary data of the index buffer } indexBuffer; + struct { - int uri; // index into the URIs array of Scene std::vector<uint8_t> data; // binary data of the vertex buffer std::vector<VertexAttribute> attributes; // description of one } vertexBuffer; + struct { float x, y, z; } min; // bounding box lower left struct { float x, y, z; } max; // bounding box upper right + int materialIndex; // index to one of the materials -} VertexGroup; +}; /** * This struct represents a single mesh as it was loaded from a glTF file. It * consists of at least one VertexGroup, which then references other resources * such as Materials. */ -typedef struct { +struct Mesh { std::string name; std::array<float, 16> modelMatrix; std::vector<int> vertexGroups; -} Mesh; - -/** - * The scene struct is simply a collection of objects in the scene as well as - * the resources used by those objects. - * Note that parent-child relations are not yet possible. - */ -typedef struct { - std::vector<Mesh> meshes; - std::vector<VertexGroup> vertexGroups; - std::vector<Material> materials; - std::vector<Texture> textures; - std::vector<Sampler> samplers; - std::vector<std::string> uris; -} Scene; - -/** - * Load every mesh from the glTF file, as well as materials, textures and other - * associated objects. - * - * @param path must be the path to a glTF- or glb-file. - * @param scene is a reference to a Scene struct that will be filled with the - * content of the glTF file being loaded. - */ -int loadScene(const std::filesystem::path &path, Scene &scene); +}; -/** - * Parse the given glTF file and create a shallow description of the content. - * Only the meta-data of the objects in the scene is loaded, not the binary - * content. The rationale is to provide a means of probing the content of a - * glTF file without the costly process of loading and decoding large amounts - * of data. The returned Scene struct can be used to search for specific meshes - * in the scene, that can then be loaded on their own using the loadMesh() - * function. Note that the Scene struct received as output argument will be - * overwritten by this function. - * After this function completes, the returned Scene struct is completely - * initialized and all information is final, except for the missing binary - * data. This means that indices to vectors will remain valid even when the - * shallow scene struct is filled with data by loadMesh(). - * - * @param path must be the path to a glTF- or glb-file. - * @param scene is a reference to a Scene struct that will be filled with the - * meta-data of all objects described in the glTF file. - */ -int probeScene(const std::filesystem::path &path, Scene &scene); + /** + * The scene struct is simply a collection of objects in the scene as well as + * the resources used by those objects. + * Note that parent-child relations are not yet possible. + */ + struct Scene { + std::vector<Mesh> meshes; + std::vector<VertexGroup> vertexGroups; + std::vector<Material> materials; + std::vector<Texture> textures; + std::vector<Sampler> samplers; + std::vector<std::string> uris; + }; -/** - * This function loads a single mesh from the given file and adds it to the - * given scene. The scene must already be initialized (via probeScene()). - * The mesh_index refers to the Scenes meshes array and identifies the mesh to - * load. To find the mesh you want, iterate over the probed scene and check the - * meshes details (eg. name). - * Besides the mesh, this function will also add any associated data to the - * Scene struct such as Materials and Textures required by the Mesh. - * - * @param path must be the path to a glTF- or glb-file. - * @param scene is the scene struct to which the results will be written. - */ -int loadMesh(Scene &scene, int mesh_index); + /** + * Parse the given glTF file and create a shallow description of the content. + * Only the meta-data of the objects in the scene is loaded, not the binary + * content. The rationale is to provide a means of probing the content of a + * glTF file without the costly process of loading and decoding large amounts + * of data. The returned Scene struct can be used to search for specific meshes + * in the scene, that can then be loaded on their own using the loadMesh() + * function. Note that the Scene struct received as output argument will be + * overwritten by this function. + * After this function completes, the returned Scene struct is completely + * initialized and all information is final, except for the missing binary + * data. This means that indices to vectors will remain valid even when the + * shallow scene struct is filled with data by loadMesh(). + * + * @param path must be the path to a glTF- or glb-file. + * @param scene is a reference to a Scene struct that will be filled with the + * meta-data of all objects described in the glTF file. + */ + int probeScene(const std::filesystem::path &path, Scene &scene); -struct TextureData { - int width; - int height; - int componentCount; - std::vector<char*> data; -}; + /** + * This function loads a single mesh from the given file and adds it to the + * given scene. The scene must already be initialized (via probeScene()). + * The mesh_index refers to the Scenes meshes array and identifies the mesh to + * load. To find the mesh you want, iterate over the probed scene and check the + * meshes details (eg. name). + * Besides the mesh, this function will also add any associated data to the + * Scene struct such as Materials and Textures required by the Mesh. + * + * @param path must be the path to a glTF- or glb-file. + * @param scene is the scene struct to which the results will be written. + */ + int loadMesh(Scene &scene, int mesh_index); + + /** + * Load every mesh from the glTF file, as well as materials, textures and other + * associated objects. + * + * @param path must be the path to a glTF- or glb-file. + * @param scene is a reference to a Scene struct that will be filled with the + * content of the glTF file being loaded. + */ + int loadScene(const std::filesystem::path &path, Scene &scene); -TextureData loadTexture(const std::filesystem::path& path); + Texture loadTexture(const std::filesystem::path& path); } diff --git a/modules/asset_loader/src/vkcv/asset/asset_loader.cpp b/modules/asset_loader/src/vkcv/asset/asset_loader.cpp index f07d4ba7..07b1c543 100644 --- a/modules/asset_loader/src/vkcv/asset/asset_loader.cpp +++ b/modules/asset_loader/src/vkcv/asset/asset_loader.cpp @@ -12,823 +12,725 @@ namespace vkcv::asset { -/** - * This function unrolls nested exceptions via recursion and prints them - * @param e The exception being thrown - * @param path The path to the file that was responsible for the exception - */ -void recurseExceptionPrint(const std::exception& e, const std::string &path) -{ - vkcv_log(LogLevel::ERROR, "Loading file %s: %s", path.c_str(), e.what()); - try { - std::rethrow_if_nested(e); - } catch (const std::exception& nested) { - recurseExceptionPrint(nested, path); - } -} - -/** - * Computes the component count for an accessor type of the fx-gltf library. - * @param type The accessor type - * @return An unsigned integer count - */ -uint8_t getCompCount(const fx::gltf::Accessor::Type type) { - switch (type) { - case fx::gltf::Accessor::Type::Scalar : - return 1; - case fx::gltf::Accessor::Type::Vec2 : - return 2; - case fx::gltf::Accessor::Type::Vec3 : - return 3; - case fx::gltf::Accessor::Type::Vec4 : - case fx::gltf::Accessor::Type::Mat2 : - return 4; - case fx::gltf::Accessor::Type::Mat3 : - return 9; - case fx::gltf::Accessor::Type::Mat4 : - return 16; - case fx::gltf::Accessor::Type::None : - default: - return ASSET_ERROR; + /** + * This function unrolls nested exceptions via recursion and prints them + * @param e The exception being thrown + * @param path The path to the file that was responsible for the exception + */ + static void recurseExceptionPrint(const std::exception& e, const std::string &path) { + vkcv_log(LogLevel::ERROR, "Loading file %s: %s", path.c_str(), e.what()); + + try { + std::rethrow_if_nested(e); + } catch (const std::exception& nested) { + recurseExceptionPrint(nested, path); + } } -} -/** - * Translate the component type used in the index accessor of fx-gltf to our - * enum for index type. The reason we have defined an incompatible enum that - * needs translation is that only a subset of component types is valid for - * indices and we want to catch these incompatibilities here. - * @param t The component type - * @return The vkcv::IndexType enum representation - */ -enum IndexType getIndexType(const enum fx::gltf::Accessor::ComponentType &type) -{ - switch (type) { - case fx::gltf::Accessor::ComponentType::UnsignedByte: - return IndexType::UINT8; - case fx::gltf::Accessor::ComponentType::UnsignedShort: - return IndexType::UINT16; - case fx::gltf::Accessor::ComponentType::UnsignedInt: - return IndexType::UINT32; - default: - vkcv_log(LogLevel::ERROR, "Index type not supported: %u", static_cast<uint16_t>(type)); - return IndexType::UNDEFINED; + /** + * Returns the component count for an accessor type of the fx-gltf library. + * @param type The accessor type + * @return An unsigned integer count + */ + static uint8_t getComponentCount(const fx::gltf::Accessor::Type type) { + switch (type) { + case fx::gltf::Accessor::Type::Scalar: + return 1; + case fx::gltf::Accessor::Type::Vec2: + return 2; + case fx::gltf::Accessor::Type::Vec3: + return 3; + case fx::gltf::Accessor::Type::Vec4: + case fx::gltf::Accessor::Type::Mat2: + return 4; + case fx::gltf::Accessor::Type::Mat3: + return 9; + case fx::gltf::Accessor::Type::Mat4: + return 16; + case fx::gltf::Accessor::Type::None: + default: + return 0; + } } -} - -int loadTexture(const Scene &scene, Texture &tex) -{ - if (tex.uri < 0 || tex.uri >= scene.uris.size()) { - vkcv_log(LogLevel::ERROR, "URI index out of range: %d", tex.uri); - } - const std::string &uri = scene.uris[tex.uri]; - int w, h, ch; - uint8_t *data = NULL; - if (!(data = stbi_load(uri.c_str(), &w, &h, &ch, 4))) { - vkcv_log(LogLevel::ERROR, "Failed to load image data from %s", - uri.c_str()); - tex.w = tex.h = tex.channels = 0; - return ASSET_ERROR; + /** + * Translate the component type used in the index accessor of fx-gltf to our + * enum for index type. The reason we have defined an incompatible enum that + * needs translation is that only a subset of component types is valid for + * indices and we want to catch these incompatibilities here. + * @param t The component type + * @return The vkcv::IndexType enum representation + */ + enum IndexType getIndexType(const enum fx::gltf::Accessor::ComponentType &type) { + switch (type) { + case fx::gltf::Accessor::ComponentType::UnsignedByte: + return IndexType::UINT8; + case fx::gltf::Accessor::ComponentType::UnsignedShort: + return IndexType::UINT16; + case fx::gltf::Accessor::ComponentType::UnsignedInt: + return IndexType::UINT32; + default: + vkcv_log(LogLevel::ERROR, "Index type not supported: %u", static_cast<uint16_t>(type)); + return IndexType::UNDEFINED; + } } - tex.channels = 4; - tex.w = static_cast<uint16_t>(w); - tex.h = static_cast<uint16_t>(h); - - tex.data.resize(tex.w * tex.h * tex.channels); - memcpy(tex.data.data(), data, tex.data.size()); - free(data); - - return ASSET_SUCCESS; -} - -/** - * This function loads all the textures of a Scene described by the texture- - * and image- array of an fx::gltf::Document. - * @param tex_src The array of textures from a fx::gltf::Document - * @param img_src The array of images from a fx::gltf::Document - * @param buf_src The Array of buffers from a fx::gltf::Document - * @param bV_src The Array of bufferViews from a fx::gltf::Document - * @param dir The path of directory in which the glTF file is located - * @param dst The array from the vkcv::Scene to write the textures to - * @return ASSET_ERROR if at least one texture could not be constructed - * properly, otherwise ASSET_SUCCESS - */ -int createTextures(const std::vector<fx::gltf::Texture>& tex_src, - const std::vector<fx::gltf::Image>& img_src, - const std::vector<fx::gltf::Buffer>& buf_src, - const std::vector<fx::gltf::BufferView>& bV_src, - const std::string& dir, std::vector<std::string> &uris, - std::vector<Texture>& dst) -{ - std::unordered_map<std::string,int> known_uris; - for (int i = 0; i < uris.size(); i++) known_uris[uris[i]] = i; - - dst.clear(); - dst.reserve(tex_src.size()); - for (const auto& tex : tex_src) { - std::string uri = dir + "/" + img_src[tex.source].uri; - int w, h, c; - uint8_t* data; - int uri_index = -1; - if (!uri.empty()) { - data = stbi_load(uri.c_str(), &w, &h, &c, 4); - if (!data) { - vkcv_log(LogLevel::ERROR, "Failed to load image data from %s", - uri.c_str()); - return ASSET_ERROR; - } - if (known_uris.count(uri)) { - uri_index = known_uris[uri]; + /** + * This function fills the array of vertex attributes of a VertexGroup (usually + * part of a vkcv::asset::Mesh) object based on the description of attributes + * for a fx::gltf::Primitive. + * + * @param src The description of attribute objects from the fx-gltf library + * @param gltf The main glTF document + * @param dst The array of vertex attributes stored in an asset::Mesh object + * @return ASSET_ERROR when at least one VertexAttribute could not be + * constructed properly, otherwise ASSET_SUCCESS + */ + static int loadVertexAttributes(const fx::gltf::Attributes &src, + const std::vector<fx::gltf::Accessor> &accessors, + const std::vector<fx::gltf::BufferView> &bufferViews, + std::vector<VertexAttribute> &dst) { + for (const auto &attrib : src) { + VertexAttribute att; + + if (attrib.first == "POSITION") { + att.type = PrimitiveType::POSITION; + } else if (attrib.first == "NORMAL") { + att.type = PrimitiveType::NORMAL; + } else if (attrib.first == "TANGENT") { + att.type = PrimitiveType::TANGENT; + } else if (attrib.first == "TEXCOORD_0") { + att.type = PrimitiveType::TEXCOORD_0; + } else if (attrib.first == "TEXCOORD_1") { + att.type = PrimitiveType::TEXCOORD_1; + } else if (attrib.first == "COLOR_0") { + att.type = PrimitiveType::COLOR_0; + } else if (attrib.first == "COLOR_1") { + att.type = PrimitiveType::COLOR_1; + } else if (attrib.first == "JOINTS_0") { + att.type = PrimitiveType::JOINTS_0; + } else if (attrib.first == "WEIGHTS_0") { + att.type = PrimitiveType::WEIGHTS_0; } else { - uri_index = uris.size(); - uris.push_back(uri); + att.type = PrimitiveType::UNDEFINED; + } + + if (att.type != PrimitiveType::UNDEFINED) { + const fx::gltf::Accessor &accessor = accessors[attrib.second]; + const fx::gltf::BufferView &buf = bufferViews[accessor.bufferView]; + + att.offset = buf.byteOffset; + att.length = buf.byteLength; + att.stride = buf.byteStride; + att.componentType = static_cast<ComponentType>(accessor.componentType); + att.componentCount = getComponentCount(accessor.type); } - } else { - const fx::gltf::BufferView bufferView = bV_src[img_src[tex.source].bufferView]; - data = stbi_load_from_memory( - &buf_src[bufferView.buffer].data[bufferView.byteOffset], - static_cast<int>(bufferView.byteLength), - &w, &h, &c, 4 - ); - if (!data) { - vkcv_log(LogLevel::ERROR, "Failed to load image data from Buffer %s", - buf_src[bufferView.buffer].name.c_str()); + if ((att.type == PrimitiveType::UNDEFINED) || + (att.componentCount == 0)) { return ASSET_ERROR; } + + dst.push_back(att); } - c = 4; - const size_t nbytes = w * h * c; - std::vector<uint8_t> imgdata; - imgdata.resize(nbytes); - if (!memcpy(imgdata.data(), data, nbytes)) { - vkcv_log(LogLevel::ERROR, "Failed to copy texture data"); - free(data); - return ASSET_ERROR; - } - free(data); - - if (uri_index < 0) vkcv_log(LogLevel::WARNING, "Texture is missing a URI."); - dst.push_back({ - uri_index, - tex.sampler, - static_cast<uint8_t>(c), - static_cast<uint16_t>(w), static_cast<uint16_t>(h), - imgdata - }); + + return ASSET_SUCCESS; } - return ASSET_SUCCESS; -} -/** - * This function fills the array of vertex attributes of a VertexGroup (usually - * part of a vkcv::asset::Mesh) object based on the description of attributes - * for a fx::gltf::Primitive. - * @param src The description of attribute objects from the fx-gltf library - * @param gltf The main glTF document - * @param dst The array of vertex attributes stored in an asset::Mesh object - * @return ASSET_ERROR when at least one VertexAttribute could not be - * constructed properly, otherwise ASSET_SUCCESS - */ -int createVertexAttributes(const fx::gltf::Attributes &src, - const std::vector<fx::gltf::Accessor> &accessors, - const std::vector<fx::gltf::BufferView> &bufferViews, - std::vector<VertexAttribute> &dst) -{ - dst.clear(); - dst.reserve(src.size()); - for (const auto &attrib : src) { - const fx::gltf::Accessor &accessor = accessors[attrib.second]; - - dst.push_back({}); - VertexAttribute &att = dst.back(); - if (attrib.first == "POSITION") { - att.type = PrimitiveType::POSITION; - } else if (attrib.first == "NORMAL") { - att.type = PrimitiveType::NORMAL; - } else if (attrib.first == "TANGENT") { - att.type = PrimitiveType::TANGENT; - } else if (attrib.first == "TEXCOORD_0") { - att.type = PrimitiveType::TEXCOORD_0; - } else if (attrib.first == "TEXCOORD_1") { - att.type = PrimitiveType::TEXCOORD_1; - } else if (attrib.first == "COLOR_0") { - att.type = PrimitiveType::COLOR_0; - } else if (attrib.first == "COLOR_1") { - att.type = PrimitiveType::COLOR_1; - } else if (attrib.first == "JOINTS_0") { - att.type = PrimitiveType::JOINTS_0; - } else if (attrib.first == "WEIGHTS_0") { - att.type = PrimitiveType::WEIGHTS_0; - } else { - att.type = PrimitiveType::UNDEFINED; - return ASSET_ERROR; - } - const fx::gltf::BufferView &buf = bufferViews[accessor.bufferView]; - att.offset = buf.byteOffset; - att.length = buf.byteLength; - att.stride = buf.byteStride; - att.componentType = static_cast<ComponentType>(accessor.componentType); - att.componentCount = getCompCount(accessor.type); + /** + * This function calculates the modelMatrix out of the data given in the gltf file. + * It also checks, whether a modelMatrix was given. + * + * @param translation possible translation vector (default 0,0,0) + * @param scale possible scale vector (default 1,1,1) + * @param rotation possible rotation, given in quaternion (default 0,0,0,1) + * @param matrix possible modelmatrix (default identity) + * @return model Matrix as an array of floats + */ + static std::array<float, 16> calculateModelMatrix(const std::array<float, 3>& translation, + const std::array<float, 3>& scale, + const std::array<float, 4>& rotation, + const std::array<float, 16>& matrix){ + std::array<float, 16> modelMatrix = { + 1,0,0,0, + 0,1,0,0, + 0,0,1,0, + 0,0,0,1 + }; - if (att.componentCount == ASSET_ERROR) { - return ASSET_ERROR; + if (matrix != modelMatrix){ + return matrix; + } else { + // translation + modelMatrix[3] = translation[0]; + modelMatrix[7] = translation[1]; + modelMatrix[11] = translation[2]; + + // rotation and scale + auto a = rotation[0]; + auto q1 = rotation[1]; + auto q2 = rotation[2]; + auto q3 = rotation[3]; + + modelMatrix[0] = (2 * (a * a + q1 * q1) - 1) * scale[0]; + modelMatrix[1] = (2 * (q1 * q2 - a * q3)) * scale[1]; + modelMatrix[2] = (2 * (q1 * q3 + a * q2)) * scale[2]; + + modelMatrix[4] = (2 * (q1 * q2 + a * q3)) * scale[0]; + modelMatrix[5] = (2 * (a * a + q2 * q2) - 1) * scale[1]; + modelMatrix[6] = (2 * (q2 * q3 - a * q1)) * scale[2]; + + modelMatrix[8] = (2 * (q1 * q3 - a * q2)) * scale[0]; + modelMatrix[9] = (2 * (q2 * q3 + a * q1)) * scale[1]; + modelMatrix[10] = (2 * (a * a + q3 * q3) - 1) * scale[2]; + + // flip y, because GLTF uses y up, but vulkan -y up + modelMatrix[5] *= -1; + + return modelMatrix; } } - return ASSET_SUCCESS; -} - -/** - * This function computes the modelMatrix out of the data given in the gltf file. It also checks, whether a modelMatrix was given. - * @param translation possible translation vector (default 0,0,0) - * @param scale possible scale vector (default 1,1,1) - * @param rotation possible rotation, given in quaternion (default 0,0,0,1) - * @param matrix possible modelmatrix (default identity) - * @return model Matrix as an array of floats - */ -std::array<float, 16> computeModelMatrix(std::array<float, 3> translation, std::array<float, 3> scale, std::array<float, 4> rotation, std::array<float, 16> matrix){ - std::array<float, 16> modelMatrix = {1,0,0,0, - 0,1,0,0, - 0,0,1,0, - 0,0,0,1}; - if (matrix != modelMatrix){ - return matrix; - } else { - // translation - modelMatrix[3] = translation[0]; - modelMatrix[7] = translation[1]; - modelMatrix[11] = translation[2]; - // rotation and scale - auto a = rotation[0]; - auto q1 = rotation[1]; - auto q2 = rotation[2]; - auto q3 = rotation[3]; - - modelMatrix[0] = (2 * (a * a + q1 * q1) - 1) * scale[0]; - modelMatrix[1] = (2 * (q1 * q2 - a * q3)) * scale[1]; - modelMatrix[2] = (2 * (q1 * q3 + a * q2)) * scale[2]; - - modelMatrix[4] = (2 * (q1 * q2 + a * q3)) * scale[0]; - modelMatrix[5] = (2 * (a * a + q2 * q2) - 1) * scale[1]; - modelMatrix[6] = (2 * (q2 * q3 - a * q1)) * scale[2]; - - modelMatrix[8] = (2 * (q1 * q3 - a * q2)) * scale[0]; - modelMatrix[9] = (2 * (q2 * q3 + a * q1)) * scale[1]; - modelMatrix[10] = (2 * (a * a + q3 * q3) - 1) * scale[2]; - - // flip y, because GLTF uses y up, but vulkan -y up - modelMatrix[5] *= -1; - - return modelMatrix; - } - -} - -bool Material::hasTexture(const PBRTextureTarget t) const -{ - return textureMask & bitflag(t); -} - - /** - * This function translates a given fx-gltf-sampler-wrapping-mode-enum to its vulkan sampler-adress-mode counterpart. - * @param mode: wrapping mode of a sampler given as fx-gltf-enum - * @return int vulkan-enum representing the same wrapping mode - */ -int translateSamplerMode(const fx::gltf::Sampler::WrappingMode mode) -{ - switch (mode) { - case fx::gltf::Sampler::WrappingMode::ClampToEdge: - return VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; - case fx::gltf::Sampler::WrappingMode::MirroredRepeat: - return VK_SAMPLER_ADDRESS_MODE_MIRRORED_REPEAT; - case fx::gltf::Sampler::WrappingMode::Repeat: - default: - return VK_SAMPLER_ADDRESS_MODE_REPEAT; - } -} -/** - * If the glTF doesn't define samplers, we use the defaults defined by fx-gltf. - * The following are details about the glTF/OpenGL to Vulkan translation. - * magFilter (VkFilter?): - * GL_NEAREST -> VK_FILTER_NEAREST - * GL_LINEAR -> VK_FILTER_LINEAR - * minFilter (VkFilter?): - * mipmapMode (VkSamplerMipmapMode?): - * Vulkans minFilter and mipmapMode combined correspond to OpenGLs - * GL_minFilter_MIPMAP_mipmapMode: - * GL_NEAREST_MIPMAP_NEAREST: - * minFilter=VK_FILTER_NEAREST - * mipmapMode=VK_SAMPLER_MIPMAP_MODE_NEAREST - * GL_LINEAR_MIPMAP_NEAREST: - * minFilter=VK_FILTER_LINEAR - * mipmapMode=VK_SAMPLER_MIPMAP_MODE_NEAREST - * GL_NEAREST_MIPMAP_LINEAR: - * minFilter=VK_FILTER_NEAREST - * mipmapMode=VK_SAMPLER_MIPMAP_MODE_LINEAR - * GL_LINEAR_MIPMAP_LINEAR: - * minFilter=VK_FILTER_LINEAR - * mipmapMode=VK_SAMPLER_MIPMAP_MODE_LINEAR - * The modes of GL_LINEAR and GL_NEAREST have to be emulated using - * mipmapMode=VK_SAMPLER_MIPMAP_MODE_NEAREST with specific minLOD and maxLOD: - * GL_LINEAR: - * minFilter=VK_FILTER_LINEAR - * mipmapMode=VK_SAMPLER_MIPMAP_MODE_NEAREST - * minLOD=0, maxLOD=0.25 - * GL_NEAREST: - * minFilter=VK_FILTER_NEAREST - * mipmapMode=VK_SAMPLER_MIPMAP_MODE_NEAREST - * minLOD=0, maxLOD=0.25 - * Setting maxLOD=0 causes magnification to always be performed (using - * the defined magFilter), this may be valid if the min- and magFilter - * are equal, otherwise it won't be the expected behaviour from OpenGL - * and glTF; instead using maxLod=0.25 allows the minFilter to be - * performed while still always rounding to the base level. - * With other modes, minLOD and maxLOD default to: - * minLOD=0 - * maxLOD=VK_LOD_CLAMP_NONE - * wrapping: - * gltf has wrapS, wrapT with {clampToEdge, MirroredRepeat, Repeat} while - * Vulkan has addressModeU, addressModeV, addressModeW with values - * VK_SAMPLER_ADDRESS_MODE_{REPEAT,MIRRORED_REPEAT,CLAMP_TO_EDGE, - * CAMP_TO_BORDER,MIRROR_CLAMP_TO_EDGE} - * Translation from glTF to Vulkan is straight forward for the 3 existing - * modes, default is repeat, the other modes aren't available. - */ -int translateSampler(const fx::gltf::Sampler &src, vkcv::asset::Sampler &dst) -{ - dst.minLOD = 0; - dst.maxLOD = VK_LOD_CLAMP_NONE; - - switch (src.minFilter) { - case fx::gltf::Sampler::MinFilter::None: - case fx::gltf::Sampler::MinFilter::Nearest: - dst.minFilter = VK_FILTER_NEAREST; - dst.mipmapMode = VK_SAMPLER_MIPMAP_MODE_NEAREST; - dst.maxLOD = 0.25; - break; - case fx::gltf::Sampler::MinFilter::Linear: - dst.minFilter = VK_FILTER_LINEAR; - dst.mipmapMode = VK_SAMPLER_MIPMAP_MODE_NEAREST; - dst.maxLOD = 0.25; - break; - case fx::gltf::Sampler::MinFilter::NearestMipMapNearest: - dst.minFilter = VK_FILTER_NEAREST; - dst.mipmapMode = VK_SAMPLER_MIPMAP_MODE_NEAREST; - break; - case fx::gltf::Sampler::MinFilter::LinearMipMapNearest: - dst.minFilter = VK_FILTER_LINEAR; - dst.mipmapMode = VK_SAMPLER_MIPMAP_MODE_NEAREST; - break; - case fx::gltf::Sampler::MinFilter::NearestMipMapLinear: - dst.minFilter = VK_FILTER_NEAREST; - dst.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR; - break; - case fx::gltf::Sampler::MinFilter::LinearMipMapLinear: - dst.minFilter = VK_FILTER_LINEAR; - dst.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR; - break; - default: - break; + bool Material::hasTexture(const PBRTextureTarget target) const { + return textureMask & bitflag(target); } - switch (src.magFilter) { - case fx::gltf::Sampler::MagFilter::None: - case fx::gltf::Sampler::MagFilter::Nearest: - dst.magFilter = VK_FILTER_NEAREST; - break; - case fx::gltf::Sampler::MagFilter::Linear: - dst.magFilter = VK_FILTER_LINEAR; - break; - default: - break; + /** + * This function translates a given fx-gltf-sampler-wrapping-mode-enum to its vulkan sampler-adress-mode counterpart. + * @param mode: wrapping mode of a sampler given as fx-gltf-enum + * @return int vulkan-enum representing the same wrapping mode + */ + static int translateSamplerMode(const fx::gltf::Sampler::WrappingMode mode) { + switch (mode) { + case fx::gltf::Sampler::WrappingMode::ClampToEdge: + return VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; + case fx::gltf::Sampler::WrappingMode::MirroredRepeat: + return VK_SAMPLER_ADDRESS_MODE_MIRRORED_REPEAT; + case fx::gltf::Sampler::WrappingMode::Repeat: + default: + return VK_SAMPLER_ADDRESS_MODE_REPEAT; + } } - dst.addressModeU = translateSamplerMode(src.wrapS); - dst.addressModeV = translateSamplerMode(src.wrapT); - // There is no information about wrapping for a third axis in glTF and - // we have to hardcode this value. - dst.addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT; - - return ASSET_SUCCESS; -} - -/** - * TODO document - */ -int createVertexGroups(fx::gltf::Mesh const& objectMesh, - fx::gltf::Document &sceneObjects, - std::vector<VertexGroup> &vertexGroups, - std::vector<int> &vertexGroupsIndices, - std::vector<std::string> &uris, - int &groupCount, bool probe) { - - const size_t numVertexGroups = objectMesh.primitives.size(); - vertexGroups.reserve(numVertexGroups); - - std::unordered_map<std::string,int> known_uris; - for (int i = 0; i < uris.size(); i++) known_uris[uris[i]] = i; - - for (const auto & objectPrimitive : objectMesh.primitives) { - std::vector<VertexAttribute> vertexAttributes; - vertexAttributes.reserve(objectPrimitive.attributes.size()); - - if (createVertexAttributes(objectPrimitive.attributes, - sceneObjects.accessors, - sceneObjects.bufferViews, - vertexAttributes) - != ASSET_SUCCESS) { - vkcv_log(LogLevel::ERROR, "Failed to get vertex attributes"); - return ASSET_ERROR; + /** + * If the glTF doesn't define samplers, we use the defaults defined by fx-gltf. + * The following are details about the glTF/OpenGL to Vulkan translation. + * magFilter (VkFilter?): + * GL_NEAREST -> VK_FILTER_NEAREST + * GL_LINEAR -> VK_FILTER_LINEAR + * minFilter (VkFilter?): + * mipmapMode (VkSamplerMipmapMode?): + * Vulkans minFilter and mipmapMode combined correspond to OpenGLs + * GL_minFilter_MIPMAP_mipmapMode: + * GL_NEAREST_MIPMAP_NEAREST: + * minFilter=VK_FILTER_NEAREST + * mipmapMode=VK_SAMPLER_MIPMAP_MODE_NEAREST + * GL_LINEAR_MIPMAP_NEAREST: + * minFilter=VK_FILTER_LINEAR + * mipmapMode=VK_SAMPLER_MIPMAP_MODE_NEAREST + * GL_NEAREST_MIPMAP_LINEAR: + * minFilter=VK_FILTER_NEAREST + * mipmapMode=VK_SAMPLER_MIPMAP_MODE_LINEAR + * GL_LINEAR_MIPMAP_LINEAR: + * minFilter=VK_FILTER_LINEAR + * mipmapMode=VK_SAMPLER_MIPMAP_MODE_LINEAR + * The modes of GL_LINEAR and GL_NEAREST have to be emulated using + * mipmapMode=VK_SAMPLER_MIPMAP_MODE_NEAREST with specific minLOD and maxLOD: + * GL_LINEAR: + * minFilter=VK_FILTER_LINEAR + * mipmapMode=VK_SAMPLER_MIPMAP_MODE_NEAREST + * minLOD=0, maxLOD=0.25 + * GL_NEAREST: + * minFilter=VK_FILTER_NEAREST + * mipmapMode=VK_SAMPLER_MIPMAP_MODE_NEAREST + * minLOD=0, maxLOD=0.25 + * Setting maxLOD=0 causes magnification to always be performed (using + * the defined magFilter), this may be valid if the min- and magFilter + * are equal, otherwise it won't be the expected behaviour from OpenGL + * and glTF; instead using maxLod=0.25 allows the minFilter to be + * performed while still always rounding to the base level. + * With other modes, minLOD and maxLOD default to: + * minLOD=0 + * maxLOD=VK_LOD_CLAMP_NONE + * wrapping: + * gltf has wrapS, wrapT with {clampToEdge, MirroredRepeat, Repeat} while + * Vulkan has addressModeU, addressModeV, addressModeW with values + * VK_SAMPLER_ADDRESS_MODE_{REPEAT,MIRRORED_REPEAT,CLAMP_TO_EDGE, + * CAMP_TO_BORDER,MIRROR_CLAMP_TO_EDGE} + * Translation from glTF to Vulkan is straight forward for the 3 existing + * modes, default is repeat, the other modes aren't available. + */ + static vkcv::asset::Sampler loadSampler(const fx::gltf::Sampler &src) { + Sampler dst; + + dst.minLOD = 0; + dst.maxLOD = VK_LOD_CLAMP_NONE; + + switch (src.minFilter) { + case fx::gltf::Sampler::MinFilter::None: + case fx::gltf::Sampler::MinFilter::Nearest: + dst.minFilter = VK_FILTER_NEAREST; + dst.mipmapMode = VK_SAMPLER_MIPMAP_MODE_NEAREST; + dst.maxLOD = 0.25; + break; + case fx::gltf::Sampler::MinFilter::Linear: + dst.minFilter = VK_FILTER_LINEAR; + dst.mipmapMode = VK_SAMPLER_MIPMAP_MODE_NEAREST; + dst.maxLOD = 0.25; + break; + case fx::gltf::Sampler::MinFilter::NearestMipMapNearest: + dst.minFilter = VK_FILTER_NEAREST; + dst.mipmapMode = VK_SAMPLER_MIPMAP_MODE_NEAREST; + break; + case fx::gltf::Sampler::MinFilter::LinearMipMapNearest: + dst.minFilter = VK_FILTER_LINEAR; + dst.mipmapMode = VK_SAMPLER_MIPMAP_MODE_NEAREST; + break; + case fx::gltf::Sampler::MinFilter::NearestMipMapLinear: + dst.minFilter = VK_FILTER_NEAREST; + dst.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR; + break; + case fx::gltf::Sampler::MinFilter::LinearMipMapLinear: + dst.minFilter = VK_FILTER_LINEAR; + dst.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR; + break; + default: + break; } - // The accessor for the position attribute is used for - // 1) getting the vertex buffer view which is only needed to get - // the vertex buffer - // 2) getting the vertex count for the VertexGroup - // 3) getting the min/max of the bounding box for the VertexGroup - fx::gltf::Accessor posAccessor; - for (auto const& attrib : objectPrimitive.attributes) { - if (attrib.first == "POSITION") { - posAccessor = sceneObjects.accessors[attrib.second]; - break; - } + + switch (src.magFilter) { + case fx::gltf::Sampler::MagFilter::None: + case fx::gltf::Sampler::MagFilter::Nearest: + dst.magFilter = VK_FILTER_NEAREST; + break; + case fx::gltf::Sampler::MagFilter::Linear: + dst.magFilter = VK_FILTER_LINEAR; + break; + default: + break; } + + dst.addressModeU = translateSamplerMode(src.wrapS); + dst.addressModeV = translateSamplerMode(src.wrapT); + + // There is no information about wrapping for a third axis in glTF and + // we have to hardcode this value. + dst.addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT; + + return dst; + } - IndexType indexType; - std::vector<uint8_t> indexBufferData = {}; - const fx::gltf::Accessor& indexAccessor = sceneObjects.accessors[objectPrimitive.indices]; - int indexBufferURI; - if (objectPrimitive.indices >= 0) { // if there is no index buffer, -1 is returned from fx-gltf - const fx::gltf::BufferView& indexBufferView = sceneObjects.bufferViews[indexAccessor.bufferView]; - const fx::gltf::Buffer& indexBuffer = sceneObjects.buffers[indexBufferView.buffer]; - if (known_uris.count(indexBuffer.uri)) { - indexBufferURI = known_uris[indexBuffer.uri]; - } else { - indexBufferURI = uris.size(); - uris.push_back(indexBuffer.uri); + /** + * TODO document + */ + static int loadVertexGroups(const fx::gltf::Mesh &objectMesh, + const fx::gltf::Document &sceneObjects, + Scene &scene, Mesh &mesh) { + mesh.vertexGroups.reserve(objectMesh.primitives.size()); + + for (const auto & objectPrimitive : objectMesh.primitives) { + VertexGroup vertexGroup; + + vertexGroup.vertexBuffer.attributes.reserve( + objectPrimitive.attributes.size() + ); + + if (ASSET_SUCCESS != loadVertexAttributes( + objectPrimitive.attributes, + sceneObjects.accessors, + sceneObjects.bufferViews, + vertexGroup.vertexBuffer.attributes)) { + vkcv_log(LogLevel::ERROR, "Failed to get vertex attributes of '%s'", + mesh.name.c_str()); + return ASSET_ERROR; } - - indexBufferData.resize(indexBufferView.byteLength); - if (!probe) { - const size_t off = indexBufferView.byteOffset; - const void* const ptr = ((char*)indexBuffer.data.data()) + off; - if (!memcpy(indexBufferData.data(), ptr, indexBufferView.byteLength)) { - vkcv_log(LogLevel::ERROR, "Copying index buffer data"); - return ASSET_ERROR; + + // The accessor for the position attribute is used for + // 1) getting the vertex buffer view which is only needed to get + // the vertex buffer + // 2) getting the vertex count for the VertexGroup + // 3) getting the min/max of the bounding box for the VertexGroup + fx::gltf::Accessor posAccessor; + bool noPosition = true; + + for (auto const& attrib : objectPrimitive.attributes) { + if (attrib.first == "POSITION") { + posAccessor = sceneObjects.accessors[attrib.second]; + noPosition = false; + break; } } - } - indexType = getIndexType(indexAccessor.componentType); - if (indexType == IndexType::UNDEFINED) { - vkcv_log(LogLevel::ERROR, "Index Type undefined or not supported."); - return ASSET_ERROR; - } - - const fx::gltf::BufferView& vertexBufferView = sceneObjects.bufferViews[posAccessor.bufferView]; - const fx::gltf::Buffer& vertexBuffer = sceneObjects.buffers[vertexBufferView.buffer]; - int vertexBufferURI; - if (known_uris.count(vertexBuffer.uri)) { - vertexBufferURI = known_uris[vertexBuffer.uri]; - } else { - vertexBufferURI = uris.size(); - uris.push_back(vertexBuffer.uri); - } - - // only copy relevant part of vertex data - uint32_t relevantBufferOffset = std::numeric_limits<uint32_t>::max(); - uint32_t relevantBufferEnd = 0; - for (const auto& attribute : vertexAttributes) { - relevantBufferOffset = std::min(attribute.offset, relevantBufferOffset); - const uint32_t attributeEnd = attribute.offset + attribute.length; - relevantBufferEnd = std::max(relevantBufferEnd, attributeEnd); - } - const uint32_t relevantBufferSize = relevantBufferEnd - relevantBufferOffset; - - std::vector<uint8_t> vertexBufferData; - if (!probe) { - vertexBufferData.resize(relevantBufferSize); - { - const void* const ptr = ((char*)vertexBuffer.data.data()) + relevantBufferOffset; - if (!memcpy(vertexBufferData.data(), ptr, relevantBufferSize)) { - vkcv_log(LogLevel::ERROR, "Copying vertex buffer data"); - return ASSET_ERROR; - } + + if (noPosition) { + vkcv_log(LogLevel::ERROR, "Position attribute not found from '%s'", + mesh.name.c_str()); + return ASSET_ERROR; } - + + const fx::gltf::Accessor& indexAccessor = sceneObjects.accessors[objectPrimitive.indices]; + + int indexBufferURI; + if (objectPrimitive.indices >= 0) { // if there is no index buffer, -1 is returned from fx-gltf + const fx::gltf::BufferView& indexBufferView = sceneObjects.bufferViews[indexAccessor.bufferView]; + const fx::gltf::Buffer& indexBuffer = sceneObjects.buffers[indexBufferView.buffer]; + + // Because the buffers are already preloaded into the memory by the gltf-library, + // it makes no sense to later them later on manually again into memory. + vertexGroup.indexBuffer.data.resize(indexBufferView.byteLength); + memcpy(vertexGroup.indexBuffer.data.data(), + indexBuffer.data.data() + indexBufferView.byteOffset, + indexBufferView.byteLength); + } else { + indexBufferURI = -1; + } + + vertexGroup.indexBuffer.type = getIndexType(indexAccessor.componentType); + + if (IndexType::UNDEFINED == vertexGroup.indexBuffer.type) { + vkcv_log(LogLevel::ERROR, "Index Type undefined or not supported."); + return ASSET_ERROR; + } + + const fx::gltf::BufferView& vertexBufferView = sceneObjects.bufferViews[posAccessor.bufferView]; + const fx::gltf::Buffer& vertexBuffer = sceneObjects.buffers[vertexBufferView.buffer]; + + // only copy relevant part of vertex data + uint32_t relevantBufferOffset = std::numeric_limits<uint32_t>::max(); + uint32_t relevantBufferEnd = 0; + + for (const auto& attribute : vertexGroup.vertexBuffer.attributes) { + relevantBufferOffset = std::min(attribute.offset, relevantBufferOffset); + relevantBufferEnd = std::max(relevantBufferEnd, attribute.offset + attribute.length); + } + + const uint32_t relevantBufferSize = relevantBufferEnd - relevantBufferOffset; + + vertexGroup.vertexBuffer.data.resize(relevantBufferSize); + memcpy(vertexGroup.vertexBuffer.data.data(), + vertexBuffer.data.data() + relevantBufferOffset, + relevantBufferSize); + // make vertex attributes relative to copied section - for (auto& attribute : vertexAttributes) { + for (auto& attribute : vertexGroup.vertexBuffer.attributes) { attribute.offset -= relevantBufferOffset; } + + vertexGroup.mode = static_cast<PrimitiveMode>(objectPrimitive.mode); + vertexGroup.numIndices = sceneObjects.accessors[objectPrimitive.indices].count; + vertexGroup.numVertices = posAccessor.count; + + memcpy(&(vertexGroup.min), posAccessor.min.data(), sizeof(vertexGroup.min)); + memcpy(&(vertexGroup.max), posAccessor.max.data(), sizeof(vertexGroup.max)); + + vertexGroup.materialIndex = static_cast<uint8_t>(objectPrimitive.material); + + mesh.vertexGroups.push_back(static_cast<int>(scene.vertexGroups.size())); + scene.vertexGroups.push_back(vertexGroup); } - vertexGroups.push_back({ - static_cast<PrimitiveMode>(objectPrimitive.mode), - sceneObjects.accessors[objectPrimitive.indices].count, - posAccessor.count, - {indexBufferURI, indexType, indexBufferData}, - {vertexBufferURI, vertexBufferData, vertexAttributes}, - {posAccessor.min[0], posAccessor.min[1], posAccessor.min[2]}, - {posAccessor.max[0], posAccessor.max[1], posAccessor.max[2]}, - static_cast<uint8_t>(objectPrimitive.material) - }); - - vertexGroupsIndices.push_back(groupCount); - groupCount++; - } - return ASSET_SUCCESS; -} - -/** - * TODO document - */ -void generateTextureMask(fx::gltf::Material &material, uint16_t &textureMask) { - if (material.pbrMetallicRoughness.baseColorTexture.index > -1) { - textureMask |= bitflag(asset::PBRTextureTarget::baseColor); + return ASSET_SUCCESS; } - if (material.pbrMetallicRoughness.metallicRoughnessTexture.index > -1) { - textureMask |= bitflag(asset::PBRTextureTarget::metalRough); - } - if (material.normalTexture.index > -1) { - textureMask |= bitflag(asset::PBRTextureTarget::normal); - } - if (material.occlusionTexture.index > -1) { - textureMask |= bitflag(asset::PBRTextureTarget::occlusion); - } - if (material.emissiveTexture.index > -1) { - textureMask |= bitflag(asset::PBRTextureTarget::emissive); - } -} - -/** - * TODO document - */ -int createMaterial(fx::gltf::Document &sceneObjects, std::vector<Material> &materials) { - if (!sceneObjects.materials.empty()) { - materials.reserve(sceneObjects.materials.size()); - for (auto material : sceneObjects.materials) { - uint16_t textureMask = 0; - generateTextureMask(material, textureMask); - materials.push_back({ - textureMask, - material.pbrMetallicRoughness.baseColorTexture.index, - material.pbrMetallicRoughness.metallicRoughnessTexture.index, - material.normalTexture.index, - material.occlusionTexture.index, - material.emissiveTexture.index, - { - material.pbrMetallicRoughness.baseColorFactor[0], - material.pbrMetallicRoughness.baseColorFactor[1], - material.pbrMetallicRoughness.baseColorFactor[2], - material.pbrMetallicRoughness.baseColorFactor[3] - }, - material.pbrMetallicRoughness.metallicFactor, - material.pbrMetallicRoughness.roughnessFactor, - material.normalTexture.scale, - material.occlusionTexture.strength, - { - material.emissiveFactor[0], - material.emissiveFactor[1], - material.emissiveFactor[2] - } - }); + /** + * TODO document + */ + static uint16_t generateTextureMask(fx::gltf::Material &material) { + uint16_t textureMask = 0; + + if (material.pbrMetallicRoughness.baseColorTexture.index >= 0) { + textureMask |= bitflag(asset::PBRTextureTarget::baseColor); } - } else { - return ASSET_ERROR; - } - return ASSET_SUCCESS; -} - -int loadScene(const std::filesystem::path &path, Scene &scene){ - fx::gltf::Document sceneObjects; - - try { - if ( path.extension() == ".glb") { - sceneObjects = fx::gltf::LoadFromBinary(path.string()); + if (material.pbrMetallicRoughness.metallicRoughnessTexture.index >= 0) { + textureMask |= bitflag(asset::PBRTextureTarget::metalRough); } - else { - sceneObjects = fx::gltf::LoadFromText(path.string()); + if (material.normalTexture.index >= 0) { + textureMask |= bitflag(asset::PBRTextureTarget::normal); } - } catch (const std::system_error& err) { - recurseExceptionPrint(err, path.string()); - return ASSET_ERROR; - } catch (const std::exception& e) { - recurseExceptionPrint(e, path.string()); - return ASSET_ERROR; - } - auto dir = path.parent_path().string(); - - // file has to contain at least one mesh - if (sceneObjects.meshes.empty()) { - return ASSET_ERROR; - } - - std::vector<Material> materials; - std::vector<Texture> textures; - std::vector<Sampler> samplers; - std::vector<Mesh> meshes; - std::vector<VertexGroup> vertexGroups; - std::vector<std::string> uris; - int groupCount = 0; - - for (size_t i = 0; i < sceneObjects.meshes.size(); i++){ - std::vector<int> vertexGroupsIndices; - fx::gltf::Mesh const &objectMesh = sceneObjects.meshes[i]; - - if (createVertexGroups(objectMesh, sceneObjects, vertexGroups, - vertexGroupsIndices, uris, groupCount, - false) != ASSET_SUCCESS) { - vkcv_log(LogLevel::ERROR, "Failed to get Vertex Groups!"); - return ASSET_ERROR; + if (material.occlusionTexture.index >= 0) { + textureMask |= bitflag(asset::PBRTextureTarget::occlusion); } - - Mesh mesh = {}; - mesh.name = sceneObjects.meshes[i].name; - mesh.vertexGroups = vertexGroupsIndices; - - meshes.push_back(mesh); - } - - // This only works if the node has a mesh and it only loads the meshes and ignores cameras and lights - for (auto & node : sceneObjects.nodes) { - if (node.mesh > -1) { - meshes[node.mesh].modelMatrix = computeModelMatrix( - node.translation, - node.scale, - node.rotation, - node.matrix - ); + if (material.emissiveTexture.index >= 0) { + textureMask |= bitflag(asset::PBRTextureTarget::emissive); } + + return textureMask; } - if (createTextures(sceneObjects.textures, sceneObjects.images,sceneObjects.buffers,sceneObjects.bufferViews, dir, uris, textures) != ASSET_SUCCESS) { - size_t missing = sceneObjects.textures.size() - textures.size(); - vkcv_log(LogLevel::ERROR, "Failed to get %lu textures from glTF source '%s'", - missing, path.c_str()); - } - - - if (createMaterial(sceneObjects, materials) != ASSET_SUCCESS) { - vkcv_log(LogLevel::ERROR, "Failed to get Materials!"); - return ASSET_ERROR; - } - - samplers.reserve(sceneObjects.samplers.size()); - for (const auto &it : sceneObjects.samplers) { - samplers.push_back({}); - auto &sampler = samplers.back(); - if (translateSampler(it, sampler) != ASSET_SUCCESS) { + int probeScene(const std::filesystem::path& path, Scene& scene) { + fx::gltf::Document sceneObjects; + + try { + if (path.extension() == ".glb") { + sceneObjects = fx::gltf::LoadFromBinary(path.string()); + } else { + sceneObjects = fx::gltf::LoadFromText(path.string()); + } + } catch (const std::system_error& err) { + recurseExceptionPrint(err, path.string()); + return ASSET_ERROR; + } catch (const std::exception& e) { + recurseExceptionPrint(e, path.string()); return ASSET_ERROR; } - } - - scene = { - meshes, - vertexGroups, - materials, - textures, - samplers - }; - - return ASSET_SUCCESS; -} - -TextureData loadTexture(const std::filesystem::path& path) { - TextureData texture; - - uint8_t* data = stbi_load(path.string().c_str(), &texture.width, &texture.height, &texture.componentCount, 4); - - if (!data) { - vkcv_log(LogLevel::ERROR, "Texture could not be loaded from '%s'", path.c_str()); - - texture.width = 0; - texture.height = 0; - texture.componentCount = 0; - return texture; - } - - texture.data.resize(texture.width * texture.height * 4); - memcpy(texture.data.data(), data, texture.data.size()); - return texture; -} - -int probeScene(const std::filesystem::path& path, Scene& scene) { - fx::gltf::Document sceneObjects; - - try { - if (path.extension() == ".glb") { - sceneObjects = fx::gltf::LoadFromBinary(path.string()); + + const auto directory = path.parent_path(); + + scene.meshes.clear(); + scene.vertexGroups.clear(); + scene.materials.clear(); + scene.textures.clear(); + scene.samplers.clear(); + + // file has to contain at least one mesh + if (sceneObjects.meshes.empty()) { + vkcv_log(LogLevel::ERROR, "No meshes found! (%s)", path.c_str()); + return ASSET_ERROR; + } else { + scene.meshes.reserve(sceneObjects.meshes.size()); + + for (size_t i = 0; i < sceneObjects.meshes.size(); i++) { + Mesh mesh; + mesh.name = sceneObjects.meshes[i].name; + + if (loadVertexGroups(sceneObjects.meshes[i], sceneObjects, scene, mesh) != ASSET_SUCCESS) { + vkcv_log(LogLevel::ERROR, "Failed to load vertex groups of '%s'! (%s)", + mesh.name.c_str(), path.c_str()); + return ASSET_ERROR; + } + + scene.meshes.push_back(mesh); + } + + // This only works if the node has a mesh and it only loads the meshes and ignores cameras and lights + for (const auto& node : sceneObjects.nodes) { + if ((node.mesh >= 0) && (node.mesh < scene.meshes.size())) { + scene.meshes[node.mesh].modelMatrix = calculateModelMatrix( + node.translation, + node.scale, + node.rotation, + node.matrix + ); + } + } } - else { - sceneObjects = fx::gltf::LoadFromText(path.string()); + + if (sceneObjects.samplers.empty()) { + vkcv_log(LogLevel::WARNING, "No samplers found! (%s)", path.c_str()); + } else { + scene.samplers.reserve(sceneObjects.samplers.size()); + + for (const auto &samplerObject : sceneObjects.samplers) { + scene.samplers.push_back(loadSampler(samplerObject)); + } } - } - catch (const std::system_error& err) { - recurseExceptionPrint(err, path.string()); - return ASSET_ERROR; - } - catch (const std::exception& e) { - recurseExceptionPrint(e, path.string()); - return ASSET_ERROR; - } - auto dir = path.parent_path().string(); - - // file has to contain at least one mesh - if (sceneObjects.meshes.empty()) { - return ASSET_ERROR; - } - - std::vector<Material> materials; - std::vector<Texture> textures; - std::vector<Sampler> samplers; - std::vector<Mesh> meshes; - std::vector<VertexGroup> vertexGroups; - std::vector<std::string> uris; - int groupCount = 0; - - std::set<std::string> names; - for (size_t i = 0; i < sceneObjects.meshes.size(); i++) { - std::vector<int> vertexGroupsIndices; - fx::gltf::Mesh const& objectMesh = sceneObjects.meshes[i]; - - if (createVertexGroups(objectMesh, sceneObjects, vertexGroups, - vertexGroupsIndices, uris, groupCount, - true) != ASSET_SUCCESS) { - vkcv_log(LogLevel::ERROR, "Failed to get Vertex Groups!"); - return ASSET_ERROR; + + if (sceneObjects.textures.empty()) { + vkcv_log(LogLevel::WARNING, "No textures found! (%s)", path.c_str()); + } else { + scene.textures.reserve(sceneObjects.textures.size()); + + for (const auto& textureObject : sceneObjects.textures) { + if ((textureObject.sampler < 0) || + (static_cast<size_t>(textureObject.sampler) >= scene.samplers.size())) { + vkcv_log(LogLevel::WARNING, "Sampler of texture '%s' missing (%s)", + textureObject.name.c_str(), path.c_str()); + return ASSET_ERROR; + } + + Texture texture; + texture.sampler = textureObject.sampler; + + if ((textureObject.source < 0) || + (static_cast<size_t>(textureObject.source) >= sceneObjects.images.size())) { + vkcv_log(LogLevel::WARNING, "Failed to load texture '%s' (%s)", + textureObject.name.c_str(), path.c_str()); + return ASSET_ERROR; + } + + const auto& image = sceneObjects.images[textureObject.source]; + + if (image.uri.empty()) { + const fx::gltf::BufferView bufferView = sceneObjects.bufferViews[image.bufferView]; + + texture.path.clear(); + texture.data.resize(bufferView.byteLength); + memcpy(texture.data.data(), + sceneObjects.buffers[bufferView.buffer].data.data() + bufferView.byteOffset, + bufferView.byteLength); + } else { + texture.path = directory / image.uri; + } + + scene.textures.push_back(texture); + } } - - Mesh mesh = {}; - mesh.name = sceneObjects.meshes[i].name; - mesh.vertexGroups = vertexGroupsIndices; - - meshes.push_back(mesh); - if (names.count(mesh.name)) { - vkcv_log(LogLevel::WARNING, "More than one mesh with name %s", - mesh.name.c_str()); + + if (sceneObjects.materials.empty()) { + vkcv_log(LogLevel::WARNING, "No materials found! (%s)", path.c_str()); } else { - names.insert(mesh.name); + scene.materials.reserve(sceneObjects.materials.size()); + + for (auto material : sceneObjects.materials) { + scene.materials.push_back({ + generateTextureMask(material), + material.pbrMetallicRoughness.baseColorTexture.index, + material.pbrMetallicRoughness.metallicRoughnessTexture.index, + material.normalTexture.index, + material.occlusionTexture.index, + material.emissiveTexture.index, + { + material.pbrMetallicRoughness.baseColorFactor[0], + material.pbrMetallicRoughness.baseColorFactor[1], + material.pbrMetallicRoughness.baseColorFactor[2], + material.pbrMetallicRoughness.baseColorFactor[3] + }, + material.pbrMetallicRoughness.metallicFactor, + material.pbrMetallicRoughness.roughnessFactor, + material.normalTexture.scale, + material.occlusionTexture.strength, + { + material.emissiveFactor[0], + material.emissiveFactor[1], + material.emissiveFactor[2] + } + }); + } } + + return ASSET_SUCCESS; } - - // This only works if the node has a mesh and it only loads the meshes and ignores cameras and lights - for (auto& node : sceneObjects.nodes) { - if (node.mesh > -1) { - meshes[node.mesh].modelMatrix = computeModelMatrix( - node.translation, - node.scale, - node.rotation, - node.matrix + + static int loadTextureData(Texture& texture) { + if ((texture.width > 0) && (texture.height > 0) && (texture.channels > 0) && + (!texture.data.empty())) { + return ASSET_SUCCESS; // Texture data was loaded already! + } + + uint8_t* data; + + if (texture.path.empty()) { + data = stbi_load_from_memory( + reinterpret_cast<uint8_t*>(texture.data.data()), + static_cast<int>(texture.data.size()), + &texture.width, + &texture.height, + &texture.channels, 4 + ); + } else { + data = stbi_load( + texture.path.c_str(), + &texture.width, + &texture.height, + &texture.channels, 4 ); } + + if (!data) { + vkcv_log(LogLevel::ERROR, "Texture could not be loaded from '%s'", + texture.path.c_str()); + + texture.width = 0; + texture.height = 0; + texture.channels = 0; + return ASSET_ERROR; + } + + texture.data.resize(texture.width * texture.height * 4); + memcpy(texture.data.data(), data, texture.data.size()); + stbi_image_free(data); + + return ASSET_SUCCESS; } - /*if (createTextures(sceneObjects.textures, sceneObjects.images, sceneObjects.buffers, sceneObjects.bufferViews, dir, uris, textures) != ASSET_SUCCESS) { - size_t missing = sceneObjects.textures.size() - textures.size(); - vkcv_log(LogLevel::ERROR, "Failed to get %lu textures from glTF source '%s'", - missing, path.c_str()); - }*/ - - - if (createMaterial(sceneObjects, materials) != ASSET_SUCCESS) { - vkcv_log(LogLevel::ERROR, "Failed to get Materials!"); - return ASSET_ERROR; - } - - /*samplers.reserve(sceneObjects.samplers.size()); - for (const auto& it : sceneObjects.samplers) { - samplers.push_back({}); - auto& sampler = samplers.back(); - if (translateSampler(it, sampler) != ASSET_SUCCESS) { + int loadMesh(Scene &scene, int index) { + if ((index < 0) || (static_cast<size_t>(index) >= scene.meshes.size())) { + vkcv_log(LogLevel::ERROR, "Mesh index out of range: %d", index); return ASSET_ERROR; } - }*/ - - scene = { - meshes, - vertexGroups, - materials, - textures, - samplers - }; - - return ASSET_SUCCESS; -} - - -int loadMesh(Scene &scene, int idx) -{ - if (idx < 0 || (size_t)idx >= scene.meshes.size()) { - vkcv_log(LogLevel::ERROR, "mesh index out of range: %d", idx); - return ASSET_ERROR; + + const Mesh &mesh = scene.meshes[index]; + + for (const auto& vg : mesh.vertexGroups) { + const VertexGroup &vertexGroup = scene.vertexGroups[vg]; + const Material& material = scene.materials[vertexGroup.materialIndex]; + + if (material.hasTexture(PBRTextureTarget::baseColor)) { + const int result = loadTextureData(scene.textures[material.baseColor]); + if (ASSET_SUCCESS != result) { + vkcv_log(LogLevel::ERROR, "Failed loading baseColor texture of mesh '%s'", + mesh.name.c_str()) + return result; + } + } + + if (material.hasTexture(PBRTextureTarget::metalRough)) { + const int result = loadTextureData(scene.textures[material.metalRough]); + if (ASSET_SUCCESS != result) { + vkcv_log(LogLevel::ERROR, "Failed loading metalRough texture of mesh '%s'", + mesh.name.c_str()) + return result; + } + } + + if (material.hasTexture(PBRTextureTarget::normal)) { + const int result = loadTextureData(scene.textures[material.normal]); + if (ASSET_SUCCESS != result) { + vkcv_log(LogLevel::ERROR, "Failed loading normal texture of mesh '%s'", + mesh.name.c_str()) + return result; + } + } + + if (material.hasTexture(PBRTextureTarget::occlusion)) { + const int result = loadTextureData(scene.textures[material.occlusion]); + if (ASSET_SUCCESS != result) { + vkcv_log(LogLevel::ERROR, "Failed loading occlusion texture of mesh '%s'", + mesh.name.c_str()) + return result; + } + } + + if (material.hasTexture(PBRTextureTarget::emissive)) { + const int result = loadTextureData(scene.textures[material.emissive]); + if (ASSET_SUCCESS != result) { + vkcv_log(LogLevel::ERROR, "Failed loading emissive texture of mesh '%s'", + mesh.name.c_str()) + return result; + } + } + } + + return ASSET_SUCCESS; + } + + int loadScene(const std::filesystem::path &path, Scene &scene) { + int result = probeScene(path, scene); + + if (result != ASSET_SUCCESS) { + vkcv_log(LogLevel::ERROR, "Loading scene failed '%s'", + path.c_str()); + return result; + } + + for (size_t i = 0; i < scene.meshes.size(); i++) { + result = loadMesh(scene, static_cast<int>(i)); + + if (result != ASSET_SUCCESS) { + vkcv_log(LogLevel::ERROR, "Loading mesh with index %d failed '%s'", + static_cast<int>(i), path.c_str()); + return result; + } + } + + return ASSET_SUCCESS; + } + + Texture loadTexture(const std::filesystem::path& path) { + Texture texture; + texture.path = path; + texture.sampler = -1; + loadTextureData(texture); + return texture; } - Mesh &mesh = scene.meshes[idx]; - - // TODO go through all vertex groups of the mesh and load the - // associated materials and index/vertex buffers - return ASSET_SUCCESS; -} } -- GitLab