diff --git a/source/Irrlicht/CGLTFMeshFileLoader.cpp b/source/Irrlicht/CGLTFMeshFileLoader.cpp new file mode 100644 index 00000000..221916bd --- /dev/null +++ b/source/Irrlicht/CGLTFMeshFileLoader.cpp @@ -0,0 +1,361 @@ +#include "CGLTFMeshFileLoader.h" +#include "CMeshBuffer.h" +#include "coreutil.h" +#include "IAnimatedMesh.h" +#include "ILogger.h" +#include "IReadFile.h" +#include "irrTypes.h" +#include "os.h" +#include "path.h" +#include "S3DVertex.h" +#include "SAnimatedMesh.h" +#include "SColor.h" +#include "SMesh.h" +#include "vector3d.h" + +#define TINYGLTF_IMPLEMENTATION +#include + +#include +#include +#include +#include +#include +#include + +/* Notes on the coordinate system. + * + * glTF uses a right-handed coordinate system where +Z is the + * front-facing axis, and Irrlicht uses a left-handed coordinate + * system where -Z is the front-facing axis. + * We convert between them by reflecting the mesh across the X axis. + * Doing this correctly requires negating the Z coordinate on + * vertex positions and normals, and reversing the winding order + * of the vertex indices. + */ + +// A helper function to disable tinygltf embedded image loading +static bool dummyImageLoader(tinygltf::Image *a, + const int b, std::string *c, + std::string *d, + int e, + int f, + const unsigned char * g, + int h, + void *userPointer) +{ + return false; +}; + +namespace irr { + +namespace scene { + +CGLTFMeshFileLoader::BufferOffset::BufferOffset( + const std::vector& buf, + const std::size_t offset) + : m_buf(buf) + , m_offset(offset) +{ +} + +CGLTFMeshFileLoader::BufferOffset::BufferOffset( + const CGLTFMeshFileLoader::BufferOffset& other, + const std::size_t fromOffset) + : m_buf(other.m_buf) + , m_offset(other.m_offset + fromOffset) +{ +} + +unsigned char CGLTFMeshFileLoader::BufferOffset::at( + const std::size_t fromOffset) const +{ + return m_buf.at(m_offset + fromOffset); +} + +CGLTFMeshFileLoader::CGLTFMeshFileLoader() noexcept +{ +} + +bool CGLTFMeshFileLoader::isALoadableFileExtension( + const io::path& filename) const +{ + return core::hasFileExtension(filename, "gltf"); +} + +IAnimatedMesh* CGLTFMeshFileLoader::createMesh(io::IReadFile* file) +{ + tinygltf::Model model {}; + if (file->getSize() <= 0 || !tryParseGLTF(file, model)) { + return nullptr; + } + + MeshExtractor parser(std::move(model)); + SMesh* baseMesh(new SMesh {}); + loadPrimitives(parser, baseMesh); + + SAnimatedMesh* animatedMesh(new SAnimatedMesh {}); + animatedMesh->addMesh(baseMesh); + + return animatedMesh; +} + +void CGLTFMeshFileLoader::loadPrimitives( + const MeshExtractor& parser, + SMesh* mesh) +{ + for (std::size_t i = 0; i < parser.getMeshCount(); ++i) { + for (std::size_t j = 0; j < parser.getPrimitiveCount(i); ++j) { + auto indices = parser.getIndices(i, j); + auto vertices = parser.getVertices(i, j); + + SMeshBuffer* meshbuf(new SMeshBuffer {}); + meshbuf->append(vertices.data(), vertices.size(), + indices.data(), indices.size()); + mesh->addMeshBuffer(meshbuf); + } + } +} + +CGLTFMeshFileLoader::MeshExtractor::MeshExtractor( + const tinygltf::Model& model) noexcept + : m_model(model) +{ +} + +CGLTFMeshFileLoader::MeshExtractor::MeshExtractor( + const tinygltf::Model&& model) noexcept + : m_model(model) +{ +} + +std::vector CGLTFMeshFileLoader::MeshExtractor::getIndices( + const std::size_t meshIdx, + const std::size_t primitiveIdx) const +{ + const auto accessorIdx = getIndicesAccessorIdx(meshIdx, primitiveIdx); + const auto& buf = getBuffer(accessorIdx); + + std::vector indices{}; + const auto count = getElemCount(accessorIdx); + for (std::size_t i = 0; i < count; ++i) { + std::size_t elemIdx = count - i - 1; + indices.push_back(readPrimitive( + BufferOffset(buf, elemIdx * sizeof(u16)))); + } + + return indices; +} + +std::vector CGLTFMeshFileLoader::MeshExtractor::getVertices( + const std::size_t meshIdx, + const std::size_t primitiveIdx) const +{ + const auto positionAccessorIdx = getPositionAccessorIdx( + meshIdx, primitiveIdx); + std::vector vertices{}; + vertices.resize(getElemCount(positionAccessorIdx)); + copyPositions(positionAccessorIdx, vertices); + + const auto normalAccessorIdx = getNormalAccessorIdx( + meshIdx, primitiveIdx); + if (normalAccessorIdx != static_cast(-1)) { + copyNormals(normalAccessorIdx, vertices); + } + + const auto tCoordAccessorIdx = getTCoordAccessorIdx( + meshIdx, primitiveIdx); + if (tCoordAccessorIdx != static_cast(-1)) { + copyTCoords(tCoordAccessorIdx, vertices); + } + + return vertices; +} + +std::size_t CGLTFMeshFileLoader::MeshExtractor::getMeshCount() const +{ + return m_model.meshes.size(); +} + +std::size_t CGLTFMeshFileLoader::MeshExtractor::getPrimitiveCount( + const std::size_t meshIdx) const +{ + return m_model.meshes[meshIdx].primitives.size(); +} + + +template +T CGLTFMeshFileLoader::MeshExtractor::readPrimitive( + const BufferOffset& readFrom) +{ + unsigned char d[sizeof(T)]{}; + for (std::size_t i = 0; i < sizeof(T); ++i) { + d[i] = readFrom.at(i); + } + T dest; + std::memcpy(&dest, d, sizeof(dest)); + return dest; +} + +core::vector2df CGLTFMeshFileLoader::MeshExtractor::readVec2DF( + const CGLTFMeshFileLoader::BufferOffset& readFrom) +{ + return core::vector2df(readPrimitive(readFrom), + readPrimitive(BufferOffset(readFrom, sizeof(float)))); + +} + +core::vector3df CGLTFMeshFileLoader::MeshExtractor::readVec3DF( + const BufferOffset& readFrom, + const float scale) +{ + return core::vector3df( + scale * readPrimitive(readFrom), + scale * readPrimitive(BufferOffset(readFrom, sizeof(float))), + -scale * readPrimitive(BufferOffset(readFrom, 2 * + sizeof(float)))); +} + +void CGLTFMeshFileLoader::MeshExtractor::copyPositions( + const std::size_t accessorIdx, + std::vector& vertices) const +{ + + const auto& buffer = getBuffer(accessorIdx); + const auto count = getElemCount(accessorIdx); + + for (std::size_t i = 0; i < count; i++) { + const auto v = readVec3DF(BufferOffset(buffer, + 3 * sizeof(float) * i), getScale()); + vertices[i].Pos = v; + } +} + +void CGLTFMeshFileLoader::MeshExtractor::copyNormals( + const std::size_t accessorIdx, + std::vector& vertices) const +{ + const auto& buffer = getBuffer(accessorIdx); + const auto count = getElemCount(accessorIdx); + + for (std::size_t i = 0; i < count; i++) { + const auto n = readVec3DF(BufferOffset(buffer, + 3 * sizeof(float) * i)); + vertices[i].Normal = n; + } +} + +void CGLTFMeshFileLoader::MeshExtractor::copyTCoords( + const std::size_t accessorIdx, + std::vector& vertices) const +{ + const auto& buffer = getBuffer(accessorIdx); + const auto count = getElemCount(accessorIdx); + + for (std::size_t i = 0; i < count; ++i) { + const auto t = readVec2DF(BufferOffset(buffer, + 2 * sizeof(float) * i)); + vertices[i].TCoords = t; + } +} + +float CGLTFMeshFileLoader::MeshExtractor::getScale() const +{ + if (m_model.nodes[0].scale.size() > 0) { + return static_cast(m_model.nodes[0].scale[0]); + } + + return 1.0f; +} + +std::size_t CGLTFMeshFileLoader::MeshExtractor::getElemCount( + const std::size_t accessorIdx) const +{ + return m_model.accessors[accessorIdx].count; +} + +CGLTFMeshFileLoader::BufferOffset CGLTFMeshFileLoader::MeshExtractor::getBuffer( + const std::size_t accessorIdx) const +{ + const auto& accessor = m_model.accessors[accessorIdx]; + const auto& view = m_model.bufferViews[accessor.bufferView]; + const auto& buffer = m_model.buffers[view.buffer]; + + return BufferOffset(buffer.data, view.byteOffset); +} + +std::size_t CGLTFMeshFileLoader::MeshExtractor::getIndicesAccessorIdx( + const std::size_t meshIdx, + const std::size_t primitiveIdx) const +{ + return m_model.meshes[meshIdx].primitives[primitiveIdx].indices; +} + +std::size_t CGLTFMeshFileLoader::MeshExtractor::getPositionAccessorIdx( + const std::size_t meshIdx, + const std::size_t primitiveIdx) const +{ + return m_model.meshes[meshIdx].primitives[primitiveIdx] + .attributes.find("POSITION")->second; +} + +std::size_t CGLTFMeshFileLoader::MeshExtractor::getNormalAccessorIdx( + const std::size_t meshIdx, + const std::size_t primitiveIdx) const +{ + const auto& attributes = m_model.meshes[meshIdx] + .primitives[primitiveIdx].attributes; + const auto result = attributes.find("NORMAL"); + + if (result == attributes.end()) { + return -1; + } else { + return result->second; + } +} + +std::size_t CGLTFMeshFileLoader::MeshExtractor::getTCoordAccessorIdx( + const std::size_t meshIdx, + const std::size_t primitiveIdx) const +{ + const auto& attributes = m_model.meshes[meshIdx] + .primitives[primitiveIdx].attributes; + const auto result = attributes.find("TEXCOORD_0"); + + if (result == attributes.end()) { + return -1; + } else { + return result->second; + } +} + +bool CGLTFMeshFileLoader::tryParseGLTF(io::IReadFile* file, + tinygltf::Model& model) +{ + tinygltf::TinyGLTF loader {}; + + // Stop embedded textures from making model fail to load + loader.SetImageLoader(dummyImageLoader, nullptr); + + std::string err {}; + std::string warn {}; + + auto buf = std::make_unique(file->getSize()); + file->read(buf.get(), file->getSize()); + + if (warn != "") { + os::Printer::log(warn.c_str(), ELL_WARNING); + } + + if (err != "") { + os::Printer::log(err.c_str(), ELL_ERROR); + return false; + } + + return loader.LoadASCIIFromString(&model, &err, &warn, buf.get(), + file->getSize(), "", 1); +} + +} // namespace scene + +} // namespace irr \ No newline at end of file diff --git a/source/Irrlicht/CGLTFMeshFileLoader.h b/source/Irrlicht/CGLTFMeshFileLoader.h new file mode 100644 index 00000000..633c5a7e --- /dev/null +++ b/source/Irrlicht/CGLTFMeshFileLoader.h @@ -0,0 +1,140 @@ +#ifndef __C_GLTF_MESH_FILE_LOADER_INCLUDED__ +#define __C_GLTF_MESH_FILE_LOADER_INCLUDED__ + +#include "IAnimatedMesh.h" +#include "IMeshLoader.h" +#include "IReadFile.h" +#include "irrTypes.h" +#include "path.h" +#include "S3DVertex.h" +#include "SMesh.h" +#include "vector2d.h" +#include "vector3d.h" + +#include + +#include +#include + +namespace irr +{ + +namespace scene +{ + +class CGLTFMeshFileLoader : public IMeshLoader +{ +public: + CGLTFMeshFileLoader() noexcept; + + bool isALoadableFileExtension(const io::path& filename) const override; + + IAnimatedMesh* createMesh(io::IReadFile* file) override; + +private: + class BufferOffset + { + public: + BufferOffset(const std::vector& buf, + const std::size_t offset); + + BufferOffset(const BufferOffset& other, + const std::size_t fromOffset); + + unsigned char at(const std::size_t fromOffset) const; + private: + const std::vector& m_buf; + std::size_t m_offset; + int m_filesize; + }; + + class MeshExtractor { + public: + using vertex_t = video::S3DVertex; + + MeshExtractor(const tinygltf::Model& model) noexcept; + + MeshExtractor(const tinygltf::Model&& model) noexcept; + + /* Gets indices for the given mesh/primitive. + * + * Values are return in Irrlicht winding order. + */ + std::vector getIndices(const std::size_t meshIdx, + const std::size_t primitiveIdx) const; + + std::vector getVertices(std::size_t meshIdx, + const std::size_t primitiveIdx) const; + + std::size_t getMeshCount() const; + + std::size_t getPrimitiveCount(const std::size_t meshIdx) const; + + private: + tinygltf::Model m_model; + + template + static T readPrimitive(const BufferOffset& readFrom); + + static core::vector2df readVec2DF( + const BufferOffset& readFrom); + + /* Read a vec3df from a buffer with transformations applied. + * + * Values are returned in Irrlicht coordinates. + */ + static core::vector3df readVec3DF( + const BufferOffset& readFrom, + const float scale = 1.0f); + + void copyPositions(const std::size_t accessorIdx, + std::vector& vertices) const; + + void copyNormals(const std::size_t accessorIdx, + std::vector& vertices) const; + + void copyTCoords(const std::size_t accessorIdx, + std::vector& vertices) const; + + /* Get the scale factor from the glTF mesh information. + * + * Returns 1.0f if no scale factor is present. + */ + float getScale() const; + + std::size_t getElemCount(const std::size_t accessorIdx) const; + + BufferOffset getBuffer(const std::size_t accessorIdx) const; + + std::size_t getIndicesAccessorIdx(const std::size_t meshIdx, + const std::size_t primitiveIdx) const; + + std::size_t getPositionAccessorIdx(const std::size_t meshIdx, + const std::size_t primitiveIdx) const; + + /* Get the accessor id of the normals of a primitive. + * + * -1 is returned if none are present. + */ + std::size_t getNormalAccessorIdx(const std::size_t meshIdx, + const std::size_t primitiveIdx) const; + + /* Get the accessor id for the tcoords of a primitive. + * + * -1 is returned if none are present. + */ + std::size_t getTCoordAccessorIdx(const std::size_t meshIdx, + const std::size_t primitiveIdx) const; + }; + + void loadPrimitives(const MeshExtractor& parser, SMesh* mesh); + + static bool tryParseGLTF(io::IReadFile* file, + tinygltf::Model& model); +}; + +} // namespace scene + +} // namespace irr + +#endif // __C_GLTF_MESH_FILE_LOADER_INCLUDED__ \ No newline at end of file