diff --git a/inferno/src/inferno/render/gltf.cpp b/inferno/src/inferno/render/gltf.cpp new file mode 100644 index 0000000..af95472 --- /dev/null +++ b/inferno/src/inferno/render/gltf.cpp @@ -0,0 +1,239 @@ +#include "inferno/util/json.h" +#include "nlohmann/json.hpp" + +#include "inferno/assert.h" +#include "inferno/io/gltffile.h" +#include "inferno/io/log.h" +#include "inferno/render/gltf.h" +#include + +namespace Inferno { + + Gltf::Gltf(const std::string& path) + : m_path(path) + { + auto gltf = GlTFFile::read(path); + ASSERT(gltf.first && gltf.second, "GlTF model was incomplete '{}'", path); + + json json = json::parse(gltf.first.get()); + + // Properties + + // Asset + // --------------------------------- + + { + ASSERT(Json::hasProperty(json, "asset"), "GlTF model missing required property 'asset'"); + auto asset = json["asset"]; + + auto copyright = Json::parseStringProperty(asset, "copyright", false); + auto generator = Json::parseStringProperty(asset, "generator", false); + auto version = Json::parseStringProperty(asset, "version", true); + ASSERT(version, "GlTF model missing required property 'version'"); + ASSERT(version.value().compare("2.0") == 0, "GlTF version unsupported '{}'", version.value()); + auto minVersion = Json::parseStringProperty(asset, "minVersion", false); + + if (copyright) m_model.asset.copyright = copyright.value(); + if (generator) m_model.asset.generator = generator.value(); + if (version) m_model.asset.version = version.value(); + if (minVersion) m_model.asset.minVersion = minVersion.value(); + } + + // Scene + // --------------------------------- + + // Node + // --------------------------------- + + // Mesh + // --------------------------------- + + if (Json::hasProperty(json, "meshes")) { + + auto meshes = json["meshes"]; + ASSERT(meshes.is_array(), "GlTF model property 'meshes' invalid type"); + + for (auto& [key, object] : meshes.items()) { + glTF::Mesh mesh; + parseMesh(&mesh, key, object); + m_model.meshes.emplace_back(std::move(mesh)); + } + } + + // Accessor + // --------------------------------- + + if (Json::hasProperty(json, "accessors")) { + + auto accessors = json["accessors"]; + ASSERT(accessors.is_array(), "GlTF model property 'accessors' invalid type"); + + for (auto& [key, object] : accessors.items()) { + glTF::Accessor accessor; + parseAccessor(&accessor, key, object); + m_model.accessors.emplace_back(std::move(accessor)); + } + } + + // Bufferview + // --------------------------------- + + if (Json::hasProperty(json, "bufferViews")) { + + auto bufferViews = json["bufferViews"]; + ASSERT(bufferViews.is_array(), "GlTF model property 'bufferViews' invalid type"); + + for (auto& [key, object] : bufferViews.items()) { + glTF::BufferView bufferView; + parseBufferView(&bufferView, key, object); + m_model.bufferViews.emplace_back(std::move(bufferView)); + } + } + + // Buffer + // --------------------------------- + + if (Json::hasProperty(json, "buffers")) { + + auto buffers = json["buffers"]; + ASSERT(buffers.is_array(), "GlTF model property 'buffers' invalid type"); + + for (auto& [key, object] : buffers.items()) { + glTF::Buffer buffer; + parseBuffer(&buffer, key, object); + m_model.buffers.emplace_back(std::move(buffer)); + } + } + } + + Gltf::~Gltf() + { + } + + void Gltf::parsePrimitive(glTF::Primitive* primitive, const std::string& key, const json& object) + { + auto attributes = Json::parseUnsignedObjectProperty(object, "attributes", true); + ASSERT(attributes && attributes.value().size() > 0, "GlTF primitive '{}' invalid property 'attributes'", key); + auto indices = Json::parseUnsignedProperty(object, "indices", false); + auto material = Json::parseUnsignedProperty(object, "material", false); + auto mode = Json::parseUnsignedProperty(object, "mode", false); + + if (Json::hasProperty(object, "targets")) { + + auto targets = object["targets"]; + ASSERT(targets.is_array(), "GlTF primitive '{}' property 'targets' invalid type", key); + + for (auto& targetObject : targets) { + + std::map target; + + for (auto& [key, propertyValue] : targetObject.items()) { + auto value = Json::getPropertyValue(propertyValue, json::value_t::number_unsigned); + if (value) target.emplace(std::move(key), value.value()); + } + + ASSERT(target.size() > 0, "GlTF primitive '{}' empty 'target' object", key); + primitive->targets.emplace_back(std::move(target)); + } + } + + if (attributes) primitive->attributes = attributes.value(); + if (indices) primitive->indices = indices.value(); + if (material) primitive->material = material.value(); + if (mode) primitive->mode = static_cast(mode.value()); + } + + void Gltf::parseMesh(glTF::Mesh* mesh, const std::string& key, const json& object) + { + ASSERT(Json::hasProperty(object, "primitives"), "GlTF mesh '{}' missing required property 'primitives'", key); + auto primitives = object["primitives"]; + ASSERT(primitives.is_array(), "GlTF mesh '{}' property 'primitives' invalid type", key); + for (auto& primitiveObject : primitives) { + glTF::Primitive primitive; + parsePrimitive(&primitive, key, primitiveObject); + mesh->primitives.emplace_back(std::move(primitive)); + } + + auto weights = Json::parseDoubleArrayProperty(object, "weights", false); + ASSERT(!weights || weights.value().size() > 0, "GlTF mesh '{}' empty 'weights' property", key); + + auto name = Json::parseStringProperty(object, "name", false); + + if (weights) mesh->weights = weights.value(); + mesh->name = name ? name.value() : key; + } + + void Gltf::parseAccessor(glTF::Accessor* accessor, const std::string& key, const json& object) + { + auto bufferView = Json::parseUnsignedProperty(object, "bufferView", false); + + auto byteOffset = Json::parseUnsignedProperty(object, "byteOffset", false); + + auto componentType = Json::parseUnsignedProperty(object, "componentType", true); + ASSERT(componentType, "GlTF accessor '{}' missing required property 'componentType'", key); + + auto normalized = Json::parseBoolProperty(object, "normalized", false); + + auto count = Json::parseUnsignedProperty(object, "count", true); + ASSERT(count, "GlTF accessor '{}' missing required property 'count'", key); + + auto type = Json::parseStringProperty(object, "type", true); + ASSERT(type, "GlTF accessor '{}' missing required property 'type'", key); + + auto max = Json::parseDoubleArrayProperty(object, "max", false); + ASSERT(!max || max.value().size() > 0, "GlTF accessor '{}' empty 'max' property", key); + + auto min = Json::parseDoubleArrayProperty(object, "min", false); + ASSERT(!min || min.value().size() > 0, "GlTF accessor '{}' empty 'min' property", key); + + auto name = Json::parseStringProperty(object, "name", false); + + if (bufferView) accessor->bufferView = bufferView.value(); + if (byteOffset) accessor->byteOffset = byteOffset.value(); + if (normalized) accessor->normalized = normalized.value(); + if (count) accessor->count = count.value(); + if (type) accessor->type = type.value(); + if (max) accessor->max = max.value(); + if (min) accessor->min = min.value(); + accessor->name = name ? name.value() : key; + } + + void Gltf::parseBufferView(glTF::BufferView* bufferView, const std::string& key, const json& object) + { + auto buffer = Json::parseUnsignedProperty(object, "buffer", false); + ASSERT(buffer, "GlTF bufferView '{}' missing required property 'buffer'", key); + + auto byteOffset = Json::parseUnsignedProperty(object, "byteOffset", false); + + auto byteLength = Json::parseUnsignedProperty(object, "byteLength", true); + ASSERT(byteLength, "GlTF bufferView '{}' missing required property 'byteLength'", key); + + auto byteStride = Json::parseUnsignedProperty(object, "byteStride", false); + + auto target = Json::parseUnsignedProperty(object, "target", false); + + auto name = Json::parseStringProperty(object, "name", false); + + if (buffer) bufferView->buffer = buffer.value(); + if (byteOffset) bufferView->byteOffset = byteOffset.value(); + if (byteLength) bufferView->byteLength = byteLength.value(); + if (byteStride) bufferView->byteStride = byteStride.value(); + if (target) bufferView->target = target.value(); + bufferView->name = name ? name.value() : key; + } + + void Gltf::parseBuffer(glTF::Buffer* buffer, const std::string& key, const json& object) + { + auto uri = Json::parseStringProperty(object, "buffer", false); + + auto byteLength = Json::parseUnsignedProperty(object, "byteLength", true); + ASSERT(byteLength, "GlTF buffer '{}' missing required property 'byteLength'", key); + + auto name = Json::parseStringProperty(object, "name", false); + + if (uri) buffer->uri = uri.value(); + if (byteLength) buffer->byteLength = byteLength.value(); + buffer->name = name ? name.value() : key; + } + +} // namespace Inferno diff --git a/inferno/src/inferno/render/gltf.h b/inferno/src/inferno/render/gltf.h new file mode 100644 index 0000000..071e1e8 --- /dev/null +++ b/inferno/src/inferno/render/gltf.h @@ -0,0 +1,130 @@ +#ifndef GLTF_H +#define GLTF_H + +#include // uint32_t +#include // std::shared_ptr +#include +#include // std::string +#include // std::unordered_map +#include // std::vector + +#include "inferno/util/json.h" + +namespace Inferno { + + namespace glTF { + + // https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#objects + + // https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#reference-asset + struct Asset { + std::string copyright; + std::string generator; + std::string version; // required + std::string minVersion; + }; + + // https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#primitive + struct Primitive { + std::map attributes; // required + uint32_t indices; + uint32_t material; + unsigned char mode { 4 }; + std::vector> targets; + }; + + // https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#mesh + struct Mesh { + std::vector primitives; // required + std::vector weights; + std::string name; + }; + + // https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#accessors + // https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#accessor + struct Accessor { + uint32_t bufferView; + uint32_t byteOffset { 0 }; + uint32_t componentType; // required + bool normalized { false }; + uint32_t count; // required + std::string type; // required + std::vector max; + std::vector min; + std::string name; + }; + + // https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#bufferview + struct BufferView { + uint32_t buffer; // required + uint32_t byteOffset { 0 }; + uint32_t byteLength; // required + uint32_t byteStride; + uint32_t target; + std::string name; + }; + + struct Buffer { + std::string uri; + uint32_t byteLength; // required + std::string name; + }; + + struct Model { + Asset asset; + + std::vector meshes; + std::vector accessors; + std::vector bufferViews; + std::vector buffers; + }; + + } // namespace glTF + +// ----------------------------------------- + + class Gltf { + public: + Gltf(const std::string& path); + virtual ~Gltf(); + + inline const glTF::Model& model() const { return m_model; } + + private: + static void parsePrimitive(glTF::Primitive* primitive, const std::string& key, const json& object); + static void parseMesh(glTF::Mesh* mesh, const std::string& key, const json& object); + static void parseAccessor(glTF::Accessor* accessor, const std::string& key, const json& object); + static void parseBufferView(glTF::BufferView* bufferView, const std::string& key, const json& object); + static void parseBuffer(glTF::Buffer* buffer, const std::string& key, const json& object); + + std::string m_path; + glTF::Model m_model; + }; + +// ----------------------------------------- + + class GltfManager { + public: + void initialize(); + void destroy(); + + void add(const std::string& path, const std::shared_ptr& gltf); + std::shared_ptr load(const std::string& path); + std::shared_ptr get(const std::string& path); + bool exists(const std::string& path); + + void remove(const std::string& path); + void remove(const std::shared_ptr& gltf); + + static inline GltfManager& the() { return *s_instance; } + + private: + std::unordered_map> m_gltfList; + + static GltfManager* s_instance; + }; + +} // namespace Inferno + + +#endif // GLTF_H