mirror of
https://github.com/Laupetin/OpenAssetTools.git
synced 2025-07-01 08:41:52 +00:00
feat: first draft of loading gltf models for t6
This commit is contained in:
128
src/ObjLoading/XModel/Gltf/GltfBinInput.cpp
Normal file
128
src/ObjLoading/XModel/Gltf/GltfBinInput.cpp
Normal file
@ -0,0 +1,128 @@
|
||||
#include "GltfBinInput.h"
|
||||
|
||||
#include "XModel/Gltf/GltfConstants.h"
|
||||
|
||||
#include <exception>
|
||||
#include <iostream>
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
using namespace gltf;
|
||||
|
||||
BinInput::BinInput()
|
||||
: m_buffer_size(0u)
|
||||
{
|
||||
}
|
||||
|
||||
bool BinInput::GetEmbeddedBuffer(const void*& buffer, size_t& bufferSize) const
|
||||
{
|
||||
if (!m_buffer || !m_buffer_size)
|
||||
return false;
|
||||
|
||||
buffer = m_buffer.get();
|
||||
bufferSize = m_buffer_size;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
const nlohmann::json& BinInput::GetJson() const
|
||||
{
|
||||
if (!m_json)
|
||||
throw std::exception();
|
||||
|
||||
return *m_json;
|
||||
}
|
||||
|
||||
bool BinInput::ReadGltfData(std::istream& stream)
|
||||
{
|
||||
uint32_t magic;
|
||||
if (!Read(stream, &magic, sizeof(magic), "magic"))
|
||||
return false;
|
||||
|
||||
if (magic != GLTF_MAGIC)
|
||||
{
|
||||
std::cerr << "Invalid magic when trying to read GLB\n";
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t version;
|
||||
if (!Read(stream, &version, sizeof(version), "version"))
|
||||
return false;
|
||||
|
||||
if (version != GLTF_VERSION)
|
||||
{
|
||||
std::cerr << std::format("Unsupported version {} when trying to read GLB: Expected version {}\n", version, GLTF_VERSION);
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t fileLength;
|
||||
if (!Read(stream, &fileLength, sizeof(fileLength), "file length"))
|
||||
return false;
|
||||
|
||||
while (true)
|
||||
{
|
||||
uint32_t chunkLength;
|
||||
uint32_t chunkMagic;
|
||||
if (!Read(stream, &chunkLength, sizeof(chunkLength), "chunk length", false))
|
||||
break;
|
||||
|
||||
if (!Read(stream, &chunkMagic, sizeof(chunkMagic), "chunk magic"))
|
||||
return false;
|
||||
|
||||
if (chunkMagic == CHUNK_MAGIC_JSON)
|
||||
{
|
||||
const auto jsonBuffer = std::make_unique<uint8_t[]>(chunkLength + 1);
|
||||
if (!Read(stream, jsonBuffer.get(), chunkLength, "json"))
|
||||
return false;
|
||||
jsonBuffer[chunkLength] = 0u;
|
||||
|
||||
try
|
||||
{
|
||||
m_json = std::make_unique<nlohmann::json>(nlohmann::json::parse(jsonBuffer.get(), &jsonBuffer[chunkLength]));
|
||||
}
|
||||
catch (const nlohmann::json::exception& e)
|
||||
{
|
||||
std::cerr << std::format("Failed trying to parse JSON of GLB: {}\n", e.what());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (chunkMagic == CHUNK_MAGIC_BIN)
|
||||
{
|
||||
m_buffer = std::make_unique<uint8_t[]>(chunkLength);
|
||||
m_buffer_size = chunkLength;
|
||||
|
||||
if (!Read(stream, m_buffer.get(), m_buffer_size, "bin buffer"))
|
||||
return false;
|
||||
}
|
||||
else
|
||||
Skip(stream, chunkLength);
|
||||
|
||||
if (chunkLength % 4u > 0)
|
||||
Skip(stream, 4u - (chunkLength % 4u));
|
||||
}
|
||||
|
||||
if (!m_json)
|
||||
{
|
||||
std::cerr << "Failed to load GLB due to missing JSON\n";
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool BinInput::Read(std::istream& stream, void* dest, const size_t dataSize, const char* readTypeName, const bool errorWhenFailed)
|
||||
{
|
||||
stream.read(static_cast<char*>(dest), dataSize);
|
||||
if (stream.gcount() != dataSize)
|
||||
{
|
||||
if (errorWhenFailed)
|
||||
std::cerr << std::format("Unexpected EOF while reading GLB {}\n", readTypeName);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void BinInput::Skip(std::istream& stream, const size_t skipLength)
|
||||
{
|
||||
stream.seekg(skipLength, std::ios::cur);
|
||||
}
|
26
src/ObjLoading/XModel/Gltf/GltfBinInput.h
Normal file
26
src/ObjLoading/XModel/Gltf/GltfBinInput.h
Normal file
@ -0,0 +1,26 @@
|
||||
#pragma once
|
||||
|
||||
#include "GltfInput.h"
|
||||
|
||||
#include <istream>
|
||||
|
||||
namespace gltf
|
||||
{
|
||||
class BinInput final : public Input
|
||||
{
|
||||
public:
|
||||
BinInput();
|
||||
|
||||
bool ReadGltfData(std::istream& stream) override;
|
||||
bool GetEmbeddedBuffer(const void*& buffer, size_t& bufferSize) const override;
|
||||
[[nodiscard]] const nlohmann::json& GetJson() const override;
|
||||
|
||||
private:
|
||||
static bool Read(std::istream& stream, void* dest, size_t dataSize, const char* readTypeName, bool errorWhenFailed = true);
|
||||
static void Skip(std::istream& stream, size_t skipLength);
|
||||
|
||||
std::unique_ptr<nlohmann::json> m_json;
|
||||
std::unique_ptr<uint8_t[]> m_buffer;
|
||||
size_t m_buffer_size;
|
||||
};
|
||||
} // namespace gltf
|
24
src/ObjLoading/XModel/Gltf/GltfInput.h
Normal file
24
src/ObjLoading/XModel/Gltf/GltfInput.h
Normal file
@ -0,0 +1,24 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdlib>
|
||||
#include <nlohmann/json_fwd.hpp>
|
||||
|
||||
namespace gltf
|
||||
{
|
||||
class Input
|
||||
{
|
||||
protected:
|
||||
Input() = default;
|
||||
|
||||
public:
|
||||
virtual ~Input() = default;
|
||||
Input(const Input& other) = default;
|
||||
Input(Input&& other) noexcept = default;
|
||||
Input& operator=(const Input& other) = default;
|
||||
Input& operator=(Input&& other) noexcept = default;
|
||||
|
||||
virtual bool ReadGltfData(std::istream& stream) = 0;
|
||||
virtual bool GetEmbeddedBuffer(const void*& buffer, size_t& bufferSize) const = 0;
|
||||
[[nodiscard]] virtual const nlohmann::json& GetJson() const = 0;
|
||||
};
|
||||
} // namespace gltf
|
688
src/ObjLoading/XModel/Gltf/GltfLoader.cpp
Normal file
688
src/ObjLoading/XModel/Gltf/GltfLoader.cpp
Normal file
@ -0,0 +1,688 @@
|
||||
#include "GltfLoader.h"
|
||||
|
||||
#include "Internal/GltfAccessor.h"
|
||||
#include "Internal/GltfBuffer.h"
|
||||
#include "Internal/GltfBufferView.h"
|
||||
|
||||
#pragma warning(push, 0)
|
||||
#include <Eigen>
|
||||
#pragma warning(pop)
|
||||
|
||||
#include <deque>
|
||||
#include <exception>
|
||||
#include <format>
|
||||
#include <iostream>
|
||||
#include <limits>
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <string>
|
||||
|
||||
using namespace gltf;
|
||||
|
||||
namespace
|
||||
{
|
||||
struct AccessorsForVertex
|
||||
{
|
||||
unsigned positionAccessor;
|
||||
std::optional<unsigned> normalAccessor;
|
||||
std::optional<unsigned> colorAccessor;
|
||||
std::optional<unsigned> uvAccessor;
|
||||
std::optional<unsigned> jointsAccessor;
|
||||
std::optional<unsigned> weightsAccessor;
|
||||
|
||||
friend bool operator==(const AccessorsForVertex& lhs, const AccessorsForVertex& rhs)
|
||||
{
|
||||
return lhs.positionAccessor == rhs.positionAccessor && lhs.normalAccessor == rhs.normalAccessor && lhs.colorAccessor == rhs.colorAccessor
|
||||
&& lhs.uvAccessor == rhs.uvAccessor && lhs.jointsAccessor == rhs.jointsAccessor && lhs.weightsAccessor == rhs.weightsAccessor;
|
||||
}
|
||||
|
||||
friend bool operator!=(const AccessorsForVertex& lhs, const AccessorsForVertex& rhs)
|
||||
{
|
||||
return !(lhs == rhs);
|
||||
}
|
||||
};
|
||||
} // namespace
|
||||
|
||||
template<> struct std::hash<AccessorsForVertex>
|
||||
{
|
||||
std::size_t operator()(const AccessorsForVertex& v) const noexcept
|
||||
{
|
||||
std::size_t seed = 0x7E42C0E6;
|
||||
seed ^= (seed << 6) + (seed >> 2) + 0x47B15429 + static_cast<std::size_t>(v.positionAccessor);
|
||||
seed ^= (seed << 6) + (seed >> 2) + 0x66847B5C + std::hash<std::optional<unsigned>>()(v.normalAccessor);
|
||||
seed ^= (seed << 6) + (seed >> 2) + 0x77399D60 + std::hash<std::optional<unsigned>>()(v.colorAccessor);
|
||||
seed ^= (seed << 6) + (seed >> 2) + 0x477AF9AB + std::hash<std::optional<unsigned>>()(v.uvAccessor);
|
||||
seed ^= (seed << 6) + (seed >> 2) + 0x4421B4D9 + std::hash<std::optional<unsigned>>()(v.jointsAccessor);
|
||||
seed ^= (seed << 6) + (seed >> 2) + 0x13C2EBA1 + std::hash<std::optional<unsigned>>()(v.weightsAccessor);
|
||||
return seed;
|
||||
}
|
||||
};
|
||||
|
||||
namespace
|
||||
{
|
||||
class GltfLoadException final : std::exception
|
||||
{
|
||||
public:
|
||||
explicit GltfLoadException(std::string message)
|
||||
: m_message(std::move(message))
|
||||
{
|
||||
}
|
||||
|
||||
[[nodiscard]] const std::string& Str() const
|
||||
{
|
||||
return m_message;
|
||||
}
|
||||
|
||||
[[nodiscard]] const char* what() const override
|
||||
{
|
||||
return m_message.c_str();
|
||||
}
|
||||
|
||||
private:
|
||||
std::string m_message;
|
||||
};
|
||||
|
||||
struct ObjectToLoad
|
||||
{
|
||||
unsigned meshIndex;
|
||||
std::optional<unsigned> skinIndex;
|
||||
|
||||
ObjectToLoad(const unsigned meshIndex, const std::optional<unsigned> skinIndex)
|
||||
: meshIndex(meshIndex),
|
||||
skinIndex(skinIndex)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
class GltfLoaderImpl final : public Loader
|
||||
{
|
||||
public:
|
||||
explicit GltfLoaderImpl(const Input* input)
|
||||
: m_input(input)
|
||||
{
|
||||
}
|
||||
|
||||
static std::vector<unsigned> GetRootNodes(const JsonRoot& jRoot)
|
||||
{
|
||||
if (!jRoot.nodes || jRoot.nodes->empty())
|
||||
return {};
|
||||
|
||||
const auto nodeCount = jRoot.nodes->size();
|
||||
std::vector<unsigned> rootNodes;
|
||||
std::vector<bool> isChild(nodeCount);
|
||||
|
||||
for (const auto& node : jRoot.nodes.value())
|
||||
{
|
||||
if (!node.children)
|
||||
continue;
|
||||
|
||||
for (const auto childIndex : node.children.value())
|
||||
{
|
||||
if (childIndex >= nodeCount)
|
||||
throw GltfLoadException("Illegal child index");
|
||||
|
||||
if (isChild[childIndex])
|
||||
throw GltfLoadException("Node hierarchy is not a set of disjoint strict trees");
|
||||
|
||||
isChild[childIndex] = true;
|
||||
}
|
||||
}
|
||||
|
||||
for (auto nodeIndex = 0u; nodeIndex < nodeCount; nodeIndex++)
|
||||
{
|
||||
if (!isChild[nodeIndex])
|
||||
rootNodes.emplace_back(nodeIndex);
|
||||
}
|
||||
|
||||
return rootNodes;
|
||||
}
|
||||
|
||||
void TraverseNodes(const JsonRoot& jRoot)
|
||||
{
|
||||
// Make sure there are any nodes to traverse
|
||||
if (!jRoot.nodes || jRoot.nodes->empty())
|
||||
return;
|
||||
|
||||
std::deque<unsigned> nodeQueue;
|
||||
std::vector<unsigned> rootNodes = GetRootNodes(jRoot);
|
||||
|
||||
for (const auto rootNode : rootNodes)
|
||||
nodeQueue.emplace_back(rootNode);
|
||||
|
||||
while (!nodeQueue.empty())
|
||||
{
|
||||
const auto& node = jRoot.nodes.value()[nodeQueue.front()];
|
||||
nodeQueue.pop_front();
|
||||
|
||||
if (node.children)
|
||||
{
|
||||
for (const auto childIndex : *node.children)
|
||||
nodeQueue.emplace_back(childIndex);
|
||||
}
|
||||
|
||||
if (node.mesh)
|
||||
m_load_objects.emplace_back(*node.mesh, node.skin);
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<Accessor*> GetAccessorForIndex(const char* attributeName,
|
||||
const std::optional<unsigned> index,
|
||||
std::initializer_list<JsonAccessorType> allowedAccessorTypes,
|
||||
std::initializer_list<JsonAccessorComponentType> allowedAccessorComponentTypes) const
|
||||
{
|
||||
if (!index)
|
||||
return std::nullopt;
|
||||
|
||||
if (*index > m_accessors.size())
|
||||
throw GltfLoadException(std::format("Index for {} accessor out of bounds", attributeName));
|
||||
|
||||
auto* accessor = m_accessors[*index].get();
|
||||
|
||||
const auto maybeType = accessor->GetType();
|
||||
if (maybeType)
|
||||
{
|
||||
if (std::ranges::find(allowedAccessorTypes, *maybeType) == allowedAccessorTypes.end())
|
||||
throw GltfLoadException(std::format("Accessor for {} has unsupported type {}", attributeName, static_cast<unsigned>(*maybeType)));
|
||||
}
|
||||
|
||||
const auto maybeComponentType = accessor->GetComponentType();
|
||||
if (maybeComponentType)
|
||||
{
|
||||
if (std::ranges::find(allowedAccessorComponentTypes, *maybeComponentType) == allowedAccessorComponentTypes.end())
|
||||
throw GltfLoadException(
|
||||
std::format("Accessor for {} has unsupported component type {}", attributeName, static_cast<unsigned>(*maybeComponentType)));
|
||||
}
|
||||
|
||||
return accessor;
|
||||
}
|
||||
|
||||
static void VerifyAccessorVertexCount(const char* accessorType, const Accessor* accessor, const size_t vertexCount)
|
||||
{
|
||||
if (accessor->GetCount() != vertexCount)
|
||||
throw GltfLoadException(std::format("Element count of {} accessor does not match expected vertex count of {}", accessorType, vertexCount));
|
||||
}
|
||||
|
||||
unsigned CreateVertices(XModelCommon& common, const AccessorsForVertex& accessorsForVertex)
|
||||
{
|
||||
// clang-format off
|
||||
auto* positionAccessor = GetAccessorForIndex(
|
||||
"POSITION",
|
||||
accessorsForVertex.positionAccessor,
|
||||
{JsonAccessorType::VEC3},
|
||||
{JsonAccessorComponentType::FLOAT}
|
||||
).value_or(nullptr);
|
||||
// clang-format on
|
||||
|
||||
const auto vertexCount = positionAccessor->GetCount();
|
||||
NullAccessor nullAccessor(vertexCount);
|
||||
|
||||
// clang-format off
|
||||
auto* normalAccessor = GetAccessorForIndex(
|
||||
"NORMAL",
|
||||
accessorsForVertex.normalAccessor,
|
||||
{JsonAccessorType::VEC3},
|
||||
{JsonAccessorComponentType::FLOAT}
|
||||
).value_or(&nullAccessor);
|
||||
VerifyAccessorVertexCount("NORMAL", normalAccessor, vertexCount);
|
||||
|
||||
auto* uvAccessor = GetAccessorForIndex(
|
||||
"TEXCOORD_0",
|
||||
accessorsForVertex.uvAccessor,
|
||||
{JsonAccessorType::VEC2},
|
||||
{JsonAccessorComponentType::FLOAT, JsonAccessorComponentType::UNSIGNED_BYTE, JsonAccessorComponentType::UNSIGNED_SHORT}
|
||||
).value_or(&nullAccessor);
|
||||
VerifyAccessorVertexCount("TEXCOORD_0", uvAccessor, vertexCount);
|
||||
|
||||
auto* colorAccessor = GetAccessorForIndex(
|
||||
"COLOR_0",
|
||||
accessorsForVertex.colorAccessor,
|
||||
{JsonAccessorType::VEC3, JsonAccessorType::VEC4},
|
||||
{JsonAccessorComponentType::FLOAT, JsonAccessorComponentType::UNSIGNED_BYTE, JsonAccessorComponentType::UNSIGNED_SHORT}
|
||||
).value_or(&nullAccessor);
|
||||
VerifyAccessorVertexCount("COLOR_0", colorAccessor, vertexCount);
|
||||
|
||||
auto* jointsAccessor = GetAccessorForIndex(
|
||||
"JOINTS_0",
|
||||
accessorsForVertex.jointsAccessor,
|
||||
{JsonAccessorType::VEC4},
|
||||
{JsonAccessorComponentType::UNSIGNED_BYTE, JsonAccessorComponentType::UNSIGNED_SHORT}
|
||||
).value_or(&nullAccessor);
|
||||
VerifyAccessorVertexCount("JOINTS_0", jointsAccessor, vertexCount);
|
||||
|
||||
auto* weightsAccessor = GetAccessorForIndex(
|
||||
"WEIGHTS_0",
|
||||
accessorsForVertex.weightsAccessor,
|
||||
{JsonAccessorType::VEC4},
|
||||
{JsonAccessorComponentType::FLOAT, JsonAccessorComponentType::UNSIGNED_BYTE, JsonAccessorComponentType::UNSIGNED_SHORT}
|
||||
).value_or(&nullAccessor);
|
||||
VerifyAccessorVertexCount("WEIGHTS_0", weightsAccessor, vertexCount);
|
||||
// clang-format on
|
||||
|
||||
const auto vertexOffset = common.m_vertices.size();
|
||||
common.m_vertices.reserve(vertexOffset + vertexCount);
|
||||
common.m_vertex_bone_weights.reserve(vertexOffset + vertexCount);
|
||||
for (auto vertexIndex = 0u; vertexIndex < vertexCount; vertexIndex++)
|
||||
{
|
||||
XModelVertex vertex;
|
||||
|
||||
unsigned joints[4];
|
||||
float weights[4];
|
||||
|
||||
if (!positionAccessor->GetFloatVec3(vertexIndex, vertex.coordinates) || !normalAccessor->GetFloatVec3(vertexIndex, vertex.normal)
|
||||
|| !colorAccessor->GetFloatVec4(vertexIndex, vertex.color) || !colorAccessor->GetFloatVec2(vertexIndex, vertex.uv)
|
||||
|| !jointsAccessor->GetUnsignedVec4(vertexIndex, joints) || !weightsAccessor->GetFloatVec4(vertexIndex, weights))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
common.m_vertices.emplace_back(vertex);
|
||||
|
||||
XModelVertexBoneWeights vertexWeights{common.m_bone_weight_data.weights.size(), 0u};
|
||||
for (auto i = 0u; i < std::extent_v<decltype(joints)>; i++)
|
||||
{
|
||||
if (std::abs(weights[i]) < std::numeric_limits<float>::epsilon())
|
||||
continue;
|
||||
|
||||
common.m_bone_weight_data.weights.emplace_back(joints[i], weights[i]);
|
||||
vertexWeights.weightCount++;
|
||||
}
|
||||
|
||||
common.m_vertex_bone_weights.emplace_back(vertexWeights);
|
||||
}
|
||||
|
||||
m_vertex_offset_for_accessors.emplace(accessorsForVertex, vertexOffset);
|
||||
return vertexOffset;
|
||||
}
|
||||
|
||||
bool ConvertObject(const JsonRoot& jRoot, const JsonMeshPrimitives& primitives, XModelCommon& common, XModelObject& object)
|
||||
{
|
||||
if (!primitives.indices)
|
||||
throw GltfLoadException("Requires primitives indices");
|
||||
if (!primitives.material)
|
||||
throw GltfLoadException("Requires primitives material");
|
||||
if (primitives.mode.value_or(JsonMeshPrimitivesMode::TRIANGLES) != JsonMeshPrimitivesMode::TRIANGLES)
|
||||
throw GltfLoadException("Only triangles are supported");
|
||||
if (!primitives.attributes.POSITION)
|
||||
throw GltfLoadException("Requires primitives attribute POSITION");
|
||||
|
||||
AccessorsForVertex accessorsForVertex{
|
||||
*primitives.attributes.POSITION,
|
||||
primitives.attributes.NORMAL,
|
||||
primitives.attributes.COLOR_0,
|
||||
primitives.attributes.TEXCOORD_0,
|
||||
primitives.attributes.JOINTS_0,
|
||||
primitives.attributes.WEIGHTS_0,
|
||||
};
|
||||
|
||||
const auto existingVertices = m_vertex_offset_for_accessors.find(accessorsForVertex);
|
||||
const auto vertexOffset =
|
||||
existingVertices == m_vertex_offset_for_accessors.end() ? CreateVertices(common, accessorsForVertex) : existingVertices->second;
|
||||
|
||||
// clang-format off
|
||||
auto* indexAccessor = GetAccessorForIndex(
|
||||
"INDICES",
|
||||
primitives.indices,
|
||||
{JsonAccessorType::SCALAR},
|
||||
{JsonAccessorComponentType::UNSIGNED_BYTE, JsonAccessorComponentType::UNSIGNED_SHORT, JsonAccessorComponentType::UNSIGNED_INT}
|
||||
).value_or(nullptr);
|
||||
// clang-format on
|
||||
|
||||
const auto indexCount = indexAccessor->GetCount();
|
||||
if (indexCount % 3 != 0)
|
||||
throw GltfLoadException("Index count must be dividable by 3 for triangles");
|
||||
const auto faceCount = indexCount / 3u;
|
||||
object.m_faces.reserve(faceCount);
|
||||
|
||||
for (auto faceIndex = 0u; faceIndex < faceCount; faceIndex++)
|
||||
{
|
||||
unsigned indices[3];
|
||||
if (!indexAccessor->GetUnsigned(faceIndex * 3u + 0u, indices[0]) || !indexAccessor->GetUnsigned(faceIndex * 3u + 1u, indices[1])
|
||||
|| !indexAccessor->GetUnsigned(faceIndex * 3u + 2u, indices[2]))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
object.m_faces.emplace_back(XModelFace{
|
||||
vertexOffset + indices[0],
|
||||
vertexOffset + indices[1],
|
||||
vertexOffset + indices[2],
|
||||
});
|
||||
}
|
||||
|
||||
if (!jRoot.materials || *primitives.material >= jRoot.materials->size())
|
||||
throw GltfLoadException("Invalid material index");
|
||||
|
||||
object.materialIndex = static_cast<int>(*primitives.material);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static std::optional<unsigned> GetRootNodeForSkin(const JsonRoot& jRoot, const JsonSkin& skin)
|
||||
{
|
||||
if (!jRoot.nodes || skin.joints.empty())
|
||||
return std::nullopt;
|
||||
|
||||
const auto jointCount = skin.joints.size();
|
||||
auto rootCount = jointCount;
|
||||
std::vector<bool> isRoot(jointCount, true);
|
||||
|
||||
for (const auto joint : skin.joints)
|
||||
{
|
||||
if (jRoot.nodes->size() <= joint)
|
||||
throw GltfLoadException("Invalid joint index");
|
||||
|
||||
const auto& node = jRoot.nodes.value()[joint];
|
||||
if (node.children)
|
||||
{
|
||||
for (const auto child : *node.children)
|
||||
{
|
||||
const auto foundChildJoint = std::ranges::find(skin.joints, child);
|
||||
if (foundChildJoint != skin.joints.end())
|
||||
{
|
||||
const auto foundChildJointIndex = std::distance(skin.joints.begin(), foundChildJoint);
|
||||
if (isRoot[foundChildJointIndex])
|
||||
{
|
||||
isRoot[foundChildJointIndex] = false;
|
||||
rootCount--;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (rootCount != 1)
|
||||
throw GltfLoadException("Skins must have exactly one common root node");
|
||||
|
||||
for (auto index = 0u; index < jointCount; index++)
|
||||
{
|
||||
if (isRoot[index])
|
||||
return skin.joints[index];
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
static bool ConvertJoint(const JsonRoot& jRoot,
|
||||
XModelCommon& common,
|
||||
const unsigned nodeIndex,
|
||||
const std::optional<unsigned> parentIndex,
|
||||
const float (&parentOffset)[3],
|
||||
const XModelQuaternion& parentRotation,
|
||||
const float (&parentScale)[3])
|
||||
{
|
||||
if (!jRoot.nodes || nodeIndex >= jRoot.nodes->size())
|
||||
return false;
|
||||
|
||||
const auto& node = jRoot.nodes.value()[nodeIndex];
|
||||
|
||||
XModelBone bone;
|
||||
bone.name = node.name.value_or(std::string());
|
||||
bone.parentIndex = parentIndex;
|
||||
|
||||
if (node.scale)
|
||||
{
|
||||
bone.scale[0] = parentScale[0] * (*node.scale)[0];
|
||||
bone.scale[1] = parentScale[1] * (*node.scale)[1];
|
||||
bone.scale[2] = parentScale[2] * (*node.scale)[2];
|
||||
}
|
||||
else
|
||||
{
|
||||
bone.scale[0] = parentScale[0];
|
||||
bone.scale[1] = parentScale[1];
|
||||
bone.scale[2] = parentScale[2];
|
||||
}
|
||||
|
||||
if (node.translation)
|
||||
{
|
||||
bone.localOffset[0] = (*node.translation)[0];
|
||||
bone.localOffset[1] = -(*node.translation)[2];
|
||||
bone.localOffset[2] = (*node.translation)[1];
|
||||
}
|
||||
else
|
||||
{
|
||||
bone.localOffset[0] = 0.0f;
|
||||
bone.localOffset[1] = 0.0f;
|
||||
bone.localOffset[2] = 0.0f;
|
||||
}
|
||||
|
||||
bone.globalOffset[0] = bone.localOffset[0] + parentOffset[0];
|
||||
bone.globalOffset[1] = bone.localOffset[1] + parentOffset[1];
|
||||
bone.globalOffset[2] = bone.localOffset[2] + parentOffset[2];
|
||||
|
||||
if (node.rotation)
|
||||
{
|
||||
bone.localRotation.x = (*node.rotation)[0];
|
||||
bone.localRotation.y = (*node.rotation)[2];
|
||||
bone.localRotation.z = -(*node.rotation)[1];
|
||||
bone.localRotation.w = (*node.rotation)[3];
|
||||
}
|
||||
else
|
||||
{
|
||||
bone.localRotation.x = 0.0f;
|
||||
bone.localRotation.y = 0.0f;
|
||||
bone.localRotation.z = 0.0f;
|
||||
bone.localRotation.w = 1.0f;
|
||||
}
|
||||
|
||||
const auto localRotationEigen = Eigen::Quaternionf(bone.localRotation.w, bone.localRotation.x, bone.localRotation.y, bone.localRotation.z);
|
||||
const auto parentRotationEigen = Eigen::Quaternionf(parentRotation.w, parentRotation.x, parentRotation.y, parentRotation.z);
|
||||
const auto globalRotationEigen = localRotationEigen * parentRotationEigen;
|
||||
|
||||
bone.globalRotation.x = globalRotationEigen.x();
|
||||
bone.globalRotation.y = globalRotationEigen.y();
|
||||
bone.globalRotation.z = globalRotationEigen.z();
|
||||
bone.globalRotation.w = globalRotationEigen.w();
|
||||
|
||||
const auto commonIndex = common.m_bones.size();
|
||||
common.m_bones.emplace_back(std::move(bone));
|
||||
|
||||
if (node.children)
|
||||
{
|
||||
for (const auto childIndex : *node.children)
|
||||
{
|
||||
if (!ConvertJoint(jRoot, common, childIndex, commonIndex, bone.globalOffset, bone.globalRotation, bone.scale))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool ConvertSkin(const JsonRoot& jRoot, const JsonSkin& skin, XModelCommon& common)
|
||||
{
|
||||
if (skin.joints.empty())
|
||||
return true;
|
||||
if (!jRoot.nodes)
|
||||
return false;
|
||||
|
||||
if (!common.m_bones.empty())
|
||||
throw GltfLoadException("Only scenes with at most one skin are supported");
|
||||
|
||||
const auto rootNode = GetRootNodeForSkin(jRoot, skin).value_or(skin.joints[0]);
|
||||
|
||||
constexpr float defaultTranslation[3]{0.0f, 0.0f, 0.0f};
|
||||
constexpr XModelQuaternion defaultRotation{0.0f, 0.0f, 0.0f, 1.0f};
|
||||
constexpr float defaultScale[3]{1.0f, 1.0f, 1.0f};
|
||||
|
||||
return ConvertJoint(jRoot, common, rootNode, std::nullopt, defaultTranslation, defaultRotation, defaultScale);
|
||||
}
|
||||
|
||||
void ConvertObjects(const JsonRoot& jRoot, XModelCommon& common)
|
||||
{
|
||||
if (!jRoot.meshes)
|
||||
return;
|
||||
|
||||
for (const auto& loadObject : m_load_objects)
|
||||
{
|
||||
if (loadObject.skinIndex && jRoot.skins)
|
||||
{
|
||||
const auto& skin = jRoot.skins.value()[*loadObject.skinIndex];
|
||||
if (!ConvertSkin(jRoot, skin, common))
|
||||
return;
|
||||
}
|
||||
|
||||
const auto& mesh = jRoot.meshes.value()[loadObject.meshIndex];
|
||||
|
||||
common.m_objects.reserve(common.m_objects.size() + mesh.primitives.size());
|
||||
for (const auto& primitives : mesh.primitives)
|
||||
{
|
||||
XModelObject object;
|
||||
if (!ConvertObject(jRoot, primitives, common, object))
|
||||
return;
|
||||
|
||||
common.m_objects.emplace_back(std::move(object));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void ConvertMaterials(const JsonRoot& jRoot, XModelCommon& common)
|
||||
{
|
||||
if (!jRoot.materials)
|
||||
return;
|
||||
|
||||
common.m_materials.reserve(jRoot.materials->size());
|
||||
for (const auto& jMaterial : *jRoot.materials)
|
||||
{
|
||||
XModelMaterial material;
|
||||
material.ApplyDefaults();
|
||||
|
||||
if (jMaterial.name)
|
||||
material.name = *jMaterial.name;
|
||||
else
|
||||
throw GltfLoadException("Materials must have a name");
|
||||
|
||||
common.m_materials.emplace_back(std::move(material));
|
||||
}
|
||||
}
|
||||
|
||||
void CreateBuffers(const JsonRoot& jRoot)
|
||||
{
|
||||
if (!jRoot.buffers)
|
||||
return;
|
||||
|
||||
m_buffers.reserve(jRoot.buffers->size());
|
||||
for (const auto& jBuffer : *jRoot.buffers)
|
||||
{
|
||||
if (!jBuffer.uri)
|
||||
{
|
||||
const void* embeddedBufferPtr = nullptr;
|
||||
size_t embeddedBufferSize = 0u;
|
||||
if (!m_input->GetEmbeddedBuffer(embeddedBufferPtr, embeddedBufferSize) || embeddedBufferSize == 0u)
|
||||
throw GltfLoadException("Buffer tried to access embedded data when there is none");
|
||||
|
||||
m_buffers.emplace_back(std::make_unique<EmbeddedBuffer>(embeddedBufferPtr, embeddedBufferSize));
|
||||
}
|
||||
else if (DataUriBuffer::IsDataUri(*jBuffer.uri))
|
||||
{
|
||||
auto dataUriBuffer = std::make_unique<DataUriBuffer>();
|
||||
if (!dataUriBuffer->ReadDataFromUri(*jBuffer.uri))
|
||||
throw GltfLoadException("Buffer has invalid data uri");
|
||||
|
||||
m_buffers.emplace_back(std::move(dataUriBuffer));
|
||||
}
|
||||
else
|
||||
{
|
||||
throw GltfLoadException("File buffers are not supported");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CreateBufferViews(const JsonRoot& jRoot)
|
||||
{
|
||||
if (!jRoot.bufferViews)
|
||||
return;
|
||||
|
||||
m_buffer_views.reserve(jRoot.bufferViews->size());
|
||||
for (const auto& jBufferView : *jRoot.bufferViews)
|
||||
{
|
||||
if (jBufferView.buffer >= m_buffers.size())
|
||||
throw GltfLoadException("Buffer view references invalid buffer");
|
||||
|
||||
const auto* buffer = m_buffers[jBufferView.buffer].get();
|
||||
const auto offset = jBufferView.byteOffset.value_or(0u);
|
||||
const auto length = jBufferView.byteLength;
|
||||
const auto stride = jBufferView.byteStride.value_or(0u);
|
||||
|
||||
if (offset + length > buffer->GetSize())
|
||||
throw GltfLoadException("Buffer view is defined larger as underlying buffer");
|
||||
|
||||
m_buffer_views.emplace_back(std::make_unique<BufferView>(buffer, offset, length, stride));
|
||||
}
|
||||
}
|
||||
|
||||
void CreateAccessors(const JsonRoot& jRoot)
|
||||
{
|
||||
if (!jRoot.accessors)
|
||||
return;
|
||||
|
||||
m_accessors.reserve(jRoot.accessors->size());
|
||||
for (const auto& jAccessor : *jRoot.accessors)
|
||||
{
|
||||
if (!jAccessor.bufferView)
|
||||
{
|
||||
m_accessors.emplace_back(std::make_unique<NullAccessor>(jAccessor.count));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (*jAccessor.bufferView >= m_buffer_views.size())
|
||||
throw GltfLoadException("Accessor references invalid buffer view");
|
||||
|
||||
const auto* bufferView = m_buffer_views[*jAccessor.bufferView].get();
|
||||
if (jAccessor.componentType == JsonAccessorComponentType::FLOAT)
|
||||
m_accessors.emplace_back(std::make_unique<FloatAccessor>(bufferView, jAccessor.type, jAccessor.count));
|
||||
else if (jAccessor.componentType == JsonAccessorComponentType::UNSIGNED_BYTE)
|
||||
m_accessors.emplace_back(std::make_unique<UnsignedByteAccessor>(bufferView, jAccessor.type, jAccessor.count));
|
||||
else if (jAccessor.componentType == JsonAccessorComponentType::UNSIGNED_SHORT)
|
||||
m_accessors.emplace_back(std::make_unique<UnsignedShortAccessor>(bufferView, jAccessor.type, jAccessor.count));
|
||||
else
|
||||
throw GltfLoadException(std::format("Accessor has unsupported component type {}", static_cast<unsigned>(jAccessor.componentType)));
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<XModelCommon> Load() override
|
||||
{
|
||||
JsonRoot jRoot;
|
||||
try
|
||||
{
|
||||
jRoot = m_input->GetJson().get<JsonRoot>();
|
||||
}
|
||||
catch (const nlohmann::json::exception& e)
|
||||
{
|
||||
std::cerr << std::format("Failed to parse GLTF JSON: {}\n", e.what());
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
CreateBuffers(jRoot);
|
||||
CreateBufferViews(jRoot);
|
||||
CreateAccessors(jRoot);
|
||||
|
||||
TraverseNodes(jRoot);
|
||||
|
||||
auto common = std::make_unique<XModelCommon>();
|
||||
ConvertObjects(jRoot, *common);
|
||||
ConvertMaterials(jRoot, *common);
|
||||
|
||||
return common;
|
||||
}
|
||||
catch (const GltfLoadException& e)
|
||||
{
|
||||
std::cerr << std::format("Failed to load GLTF: {}\n", e.Str());
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
const Input* m_input;
|
||||
std::vector<ObjectToLoad> m_load_objects;
|
||||
std::unordered_map<AccessorsForVertex, unsigned> m_vertex_offset_for_accessors;
|
||||
std::vector<std::unique_ptr<Accessor>> m_accessors;
|
||||
std::vector<std::unique_ptr<BufferView>> m_buffer_views;
|
||||
std::vector<std::unique_ptr<Buffer>> m_buffers;
|
||||
};
|
||||
} // namespace
|
||||
|
||||
std::unique_ptr<Loader> Loader::CreateLoader(const Input* input)
|
||||
{
|
||||
return std::make_unique<GltfLoaderImpl>(input);
|
||||
}
|
24
src/ObjLoading/XModel/Gltf/GltfLoader.h
Normal file
24
src/ObjLoading/XModel/Gltf/GltfLoader.h
Normal file
@ -0,0 +1,24 @@
|
||||
#pragma once
|
||||
|
||||
#include "GltfInput.h"
|
||||
#include "XModel/Gltf/JsonGltf.h"
|
||||
#include "XModel/XModelLoader.h"
|
||||
|
||||
#include <memory>
|
||||
#include <ostream>
|
||||
|
||||
namespace gltf
|
||||
{
|
||||
class Loader : public XModelLoader
|
||||
{
|
||||
public:
|
||||
Loader() = default;
|
||||
~Loader() override = default;
|
||||
Loader(const Loader& other) = default;
|
||||
Loader(Loader&& other) noexcept = default;
|
||||
Loader& operator=(const Loader& other) = default;
|
||||
Loader& operator=(Loader&& other) noexcept = default;
|
||||
|
||||
static std::unique_ptr<Loader> CreateLoader(const Input* input);
|
||||
};
|
||||
} // namespace gltf
|
48
src/ObjLoading/XModel/Gltf/GltfTextInput.cpp
Normal file
48
src/ObjLoading/XModel/Gltf/GltfTextInput.cpp
Normal file
@ -0,0 +1,48 @@
|
||||
#include "GltfTextInput.h"
|
||||
|
||||
#include <exception>
|
||||
#include <format>
|
||||
#include <iostream>
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
using namespace gltf;
|
||||
|
||||
TextInput::TextInput()
|
||||
: m_buffer_size(0u)
|
||||
{
|
||||
}
|
||||
|
||||
bool TextInput::GetEmbeddedBuffer(const void*& buffer, size_t& bufferSize) const
|
||||
{
|
||||
if (!m_buffer || !m_buffer_size)
|
||||
return false;
|
||||
|
||||
buffer = m_buffer.get();
|
||||
bufferSize = m_buffer_size;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
const nlohmann::json& TextInput::GetJson() const
|
||||
{
|
||||
if (!m_json)
|
||||
throw std::exception();
|
||||
|
||||
return *m_json;
|
||||
}
|
||||
|
||||
bool TextInput::ReadGltfData(std::istream& stream)
|
||||
{
|
||||
try
|
||||
{
|
||||
m_json = std::make_unique<nlohmann::json>(nlohmann::json::parse(stream));
|
||||
|
||||
return true;
|
||||
}
|
||||
catch (nlohmann::json::exception& e)
|
||||
{
|
||||
std::cerr << std::format("Failed to parse json of GLTF: {}", e.what());
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
23
src/ObjLoading/XModel/Gltf/GltfTextInput.h
Normal file
23
src/ObjLoading/XModel/Gltf/GltfTextInput.h
Normal file
@ -0,0 +1,23 @@
|
||||
#pragma once
|
||||
|
||||
#include "GltfInput.h"
|
||||
|
||||
#include <istream>
|
||||
|
||||
namespace gltf
|
||||
{
|
||||
class TextInput final : public Input
|
||||
{
|
||||
public:
|
||||
TextInput();
|
||||
|
||||
bool ReadGltfData(std::istream& stream) override;
|
||||
bool GetEmbeddedBuffer(const void*& buffer, size_t& bufferSize) const override;
|
||||
[[nodiscard]] const nlohmann::json& GetJson() const override;
|
||||
|
||||
private:
|
||||
std::unique_ptr<nlohmann::json> m_json;
|
||||
std::unique_ptr<uint8_t[]> m_buffer;
|
||||
size_t m_buffer_size;
|
||||
};
|
||||
} // namespace gltf
|
355
src/ObjLoading/XModel/Gltf/Internal/GltfAccessor.cpp
Normal file
355
src/ObjLoading/XModel/Gltf/Internal/GltfAccessor.cpp
Normal file
@ -0,0 +1,355 @@
|
||||
#include "GltfAccessor.h"
|
||||
|
||||
using namespace gltf;
|
||||
|
||||
NullAccessor::NullAccessor(const size_t count)
|
||||
: m_count(count)
|
||||
{
|
||||
}
|
||||
|
||||
bool NullAccessor::GetFloatVec2(const size_t index, float (&out)[2]) const
|
||||
{
|
||||
if (index >= m_count)
|
||||
return false;
|
||||
|
||||
out[0] = 0.0f;
|
||||
out[1] = 0.0f;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool NullAccessor::GetFloatVec3(const size_t index, float (&out)[3]) const
|
||||
{
|
||||
if (index >= m_count)
|
||||
return false;
|
||||
|
||||
out[0] = 0.0f;
|
||||
out[1] = 0.0f;
|
||||
out[2] = 0.0f;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool NullAccessor::GetFloatVec4(const size_t index, float (&out)[4]) const
|
||||
{
|
||||
if (index >= m_count)
|
||||
return false;
|
||||
|
||||
out[0] = 0.0f;
|
||||
out[1] = 0.0f;
|
||||
out[2] = 0.0f;
|
||||
out[3] = 0.0f;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool NullAccessor::GetUnsigned(const size_t index, unsigned& out) const
|
||||
{
|
||||
if (index >= m_count)
|
||||
return false;
|
||||
|
||||
out = 0u;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool NullAccessor::GetUnsignedVec4(const size_t index, unsigned (&out)[4]) const
|
||||
{
|
||||
if (index >= m_count)
|
||||
return false;
|
||||
|
||||
out[0] = 0u;
|
||||
out[1] = 0u;
|
||||
out[2] = 0u;
|
||||
out[3] = 0u;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
std::optional<JsonAccessorComponentType> NullAccessor::GetComponentType() const
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<JsonAccessorType> NullAccessor::GetType() const
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
size_t NullAccessor::GetCount() const
|
||||
{
|
||||
return m_count;
|
||||
}
|
||||
|
||||
FloatAccessor::FloatAccessor(const BufferView* bufferView, const JsonAccessorType type, const size_t count)
|
||||
: m_buffer_view(bufferView),
|
||||
m_type(type),
|
||||
m_count(count)
|
||||
{
|
||||
}
|
||||
|
||||
std::optional<JsonAccessorType> FloatAccessor::GetType() const
|
||||
{
|
||||
return m_type;
|
||||
}
|
||||
|
||||
std::optional<JsonAccessorComponentType> FloatAccessor::GetComponentType() const
|
||||
{
|
||||
return JsonAccessorComponentType::FLOAT;
|
||||
}
|
||||
|
||||
size_t FloatAccessor::GetCount() const
|
||||
{
|
||||
return m_count;
|
||||
}
|
||||
|
||||
bool FloatAccessor::GetFloatVec2(const size_t index, float (&out)[2]) const
|
||||
{
|
||||
assert(m_type == JsonAccessorType::VEC2 || m_type == JsonAccessorType::VEC3 || m_type == JsonAccessorType::VEC4);
|
||||
if (index >= m_count)
|
||||
return false;
|
||||
|
||||
return m_buffer_view->ReadElement(&out, index, sizeof(float[2]));
|
||||
}
|
||||
|
||||
bool FloatAccessor::GetFloatVec3(const size_t index, float (&out)[3]) const
|
||||
{
|
||||
assert(m_type == JsonAccessorType::VEC3 || m_type == JsonAccessorType::VEC4);
|
||||
if (index >= m_count)
|
||||
return false;
|
||||
|
||||
return m_buffer_view->ReadElement(&out, index, sizeof(float[3]));
|
||||
}
|
||||
|
||||
bool FloatAccessor::GetFloatVec4(const size_t index, float (&out)[4]) const
|
||||
{
|
||||
assert(m_type == JsonAccessorType::VEC4);
|
||||
if (index >= m_count)
|
||||
return false;
|
||||
|
||||
return m_buffer_view->ReadElement(&out, index, sizeof(float[4]));
|
||||
}
|
||||
|
||||
bool FloatAccessor::GetUnsigned(size_t index, unsigned& out) const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool FloatAccessor::GetUnsignedVec4(size_t index, unsigned (&out)[4]) const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
UnsignedByteAccessor::UnsignedByteAccessor(const BufferView* bufferView, const JsonAccessorType type, const size_t count)
|
||||
: m_buffer_view(bufferView),
|
||||
m_type(type),
|
||||
m_count(count)
|
||||
{
|
||||
}
|
||||
|
||||
std::optional<JsonAccessorType> UnsignedByteAccessor::GetType() const
|
||||
{
|
||||
return m_type;
|
||||
}
|
||||
|
||||
std::optional<JsonAccessorComponentType> UnsignedByteAccessor::GetComponentType() const
|
||||
{
|
||||
return JsonAccessorComponentType::UNSIGNED_BYTE;
|
||||
}
|
||||
|
||||
size_t UnsignedByteAccessor::GetCount() const
|
||||
{
|
||||
return m_count;
|
||||
}
|
||||
|
||||
bool UnsignedByteAccessor::GetFloatVec2(const size_t index, float (&out)[2]) const
|
||||
{
|
||||
assert(m_type == JsonAccessorType::VEC2 || m_type == JsonAccessorType::VEC3 || m_type == JsonAccessorType::VEC4);
|
||||
if (index >= m_count)
|
||||
return false;
|
||||
|
||||
uint8_t temp[2];
|
||||
if (!m_buffer_view->ReadElement(temp, index, sizeof(uint8_t[2])))
|
||||
return false;
|
||||
|
||||
// Return as normalized value between 0 and 1
|
||||
out[0] = static_cast<float>(temp[0]) / static_cast<float>(std::numeric_limits<uint8_t>::max());
|
||||
out[1] = static_cast<float>(temp[1]) / static_cast<float>(std::numeric_limits<uint8_t>::max());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool UnsignedByteAccessor::GetFloatVec3(const size_t index, float (&out)[3]) const
|
||||
{
|
||||
assert(m_type == JsonAccessorType::VEC3 || m_type == JsonAccessorType::VEC4);
|
||||
if (index >= m_count)
|
||||
return false;
|
||||
|
||||
uint8_t temp[3];
|
||||
if (!m_buffer_view->ReadElement(temp, index, sizeof(uint8_t[3])))
|
||||
return false;
|
||||
|
||||
// Return as normalized value between 0 and 1
|
||||
out[0] = static_cast<float>(temp[0]) / static_cast<float>(std::numeric_limits<uint8_t>::max());
|
||||
out[1] = static_cast<float>(temp[1]) / static_cast<float>(std::numeric_limits<uint8_t>::max());
|
||||
out[2] = static_cast<float>(temp[2]) / static_cast<float>(std::numeric_limits<uint8_t>::max());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool UnsignedByteAccessor::GetFloatVec4(const size_t index, float (&out)[4]) const
|
||||
{
|
||||
assert(m_type == JsonAccessorType::VEC4);
|
||||
if (index >= m_count)
|
||||
return false;
|
||||
|
||||
uint8_t temp[4];
|
||||
if (!m_buffer_view->ReadElement(temp, index, sizeof(uint8_t[4])))
|
||||
return false;
|
||||
|
||||
// Return as normalized value between 0 and 1
|
||||
out[0] = static_cast<float>(temp[0]) / static_cast<float>(std::numeric_limits<uint8_t>::max());
|
||||
out[1] = static_cast<float>(temp[1]) / static_cast<float>(std::numeric_limits<uint8_t>::max());
|
||||
out[2] = static_cast<float>(temp[2]) / static_cast<float>(std::numeric_limits<uint8_t>::max());
|
||||
out[3] = static_cast<float>(temp[3]) / static_cast<float>(std::numeric_limits<uint8_t>::max());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool UnsignedByteAccessor::GetUnsigned(const size_t index, unsigned& out) const
|
||||
{
|
||||
if (index >= m_count)
|
||||
return false;
|
||||
|
||||
uint8_t temp;
|
||||
if (!m_buffer_view->ReadElement(&temp, index, sizeof(uint8_t)))
|
||||
return false;
|
||||
|
||||
out = temp;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool UnsignedByteAccessor::GetUnsignedVec4(const size_t index, unsigned (&out)[4]) const
|
||||
{
|
||||
assert(m_type == JsonAccessorType::VEC4);
|
||||
if (index >= m_count)
|
||||
return false;
|
||||
|
||||
uint8_t temp[4];
|
||||
if (!m_buffer_view->ReadElement(temp, index, sizeof(uint8_t[4])))
|
||||
return false;
|
||||
|
||||
out[0] = static_cast<unsigned>(temp[0]);
|
||||
out[1] = static_cast<unsigned>(temp[1]);
|
||||
out[2] = static_cast<unsigned>(temp[2]);
|
||||
out[3] = static_cast<unsigned>(temp[3]);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
UnsignedShortAccessor::UnsignedShortAccessor(const BufferView* bufferView, const JsonAccessorType type, const size_t count)
|
||||
: m_buffer_view(bufferView),
|
||||
m_type(type),
|
||||
m_count(count)
|
||||
{
|
||||
}
|
||||
|
||||
std::optional<JsonAccessorType> UnsignedShortAccessor::GetType() const
|
||||
{
|
||||
return m_type;
|
||||
}
|
||||
|
||||
std::optional<JsonAccessorComponentType> UnsignedShortAccessor::GetComponentType() const
|
||||
{
|
||||
return JsonAccessorComponentType::UNSIGNED_SHORT;
|
||||
}
|
||||
|
||||
size_t UnsignedShortAccessor::GetCount() const
|
||||
{
|
||||
return m_count;
|
||||
}
|
||||
|
||||
bool UnsignedShortAccessor::GetFloatVec2(const size_t index, float (&out)[2]) const
|
||||
{
|
||||
assert(m_type == JsonAccessorType::VEC2 || m_type == JsonAccessorType::VEC3 || m_type == JsonAccessorType::VEC4);
|
||||
if (index >= m_count)
|
||||
return false;
|
||||
|
||||
uint16_t temp[2];
|
||||
if (!m_buffer_view->ReadElement(temp, index, sizeof(uint16_t[2])))
|
||||
return false;
|
||||
|
||||
// Return as normalized value between 0 and 1
|
||||
out[0] = static_cast<float>(temp[0]) / static_cast<float>(std::numeric_limits<uint16_t>::max());
|
||||
out[1] = static_cast<float>(temp[1]) / static_cast<float>(std::numeric_limits<uint16_t>::max());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool UnsignedShortAccessor::GetFloatVec3(const size_t index, float (&out)[3]) const
|
||||
{
|
||||
assert(m_type == JsonAccessorType::VEC3 || m_type == JsonAccessorType::VEC4);
|
||||
if (index >= m_count)
|
||||
return false;
|
||||
|
||||
uint16_t temp[3];
|
||||
if (!m_buffer_view->ReadElement(temp, index, sizeof(uint16_t[3])))
|
||||
return false;
|
||||
|
||||
// Return as normalized value between 0 and 1
|
||||
out[0] = static_cast<float>(temp[0]) / static_cast<float>(std::numeric_limits<uint16_t>::max());
|
||||
out[1] = static_cast<float>(temp[1]) / static_cast<float>(std::numeric_limits<uint16_t>::max());
|
||||
out[2] = static_cast<float>(temp[2]) / static_cast<float>(std::numeric_limits<uint16_t>::max());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool UnsignedShortAccessor::GetFloatVec4(const size_t index, float (&out)[4]) const
|
||||
{
|
||||
assert(m_type == JsonAccessorType::VEC4);
|
||||
if (index >= m_count)
|
||||
return false;
|
||||
|
||||
uint16_t temp[4];
|
||||
if (!m_buffer_view->ReadElement(temp, index, sizeof(uint16_t[4])))
|
||||
return false;
|
||||
|
||||
// Return as normalized value between 0 and 1
|
||||
out[0] = static_cast<float>(temp[0]) / static_cast<float>(std::numeric_limits<uint16_t>::max());
|
||||
out[1] = static_cast<float>(temp[1]) / static_cast<float>(std::numeric_limits<uint16_t>::max());
|
||||
out[2] = static_cast<float>(temp[2]) / static_cast<float>(std::numeric_limits<uint16_t>::max());
|
||||
out[3] = static_cast<float>(temp[3]) / static_cast<float>(std::numeric_limits<uint16_t>::max());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool UnsignedShortAccessor::GetUnsigned(const size_t index, unsigned& out) const
|
||||
{
|
||||
if (index >= m_count)
|
||||
return false;
|
||||
|
||||
uint16_t temp;
|
||||
if (!m_buffer_view->ReadElement(&temp, index, sizeof(uint16_t)))
|
||||
return false;
|
||||
|
||||
out = temp;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool UnsignedShortAccessor::GetUnsignedVec4(const size_t index, unsigned (&out)[4]) const
|
||||
{
|
||||
assert(m_type == JsonAccessorType::VEC4);
|
||||
if (index >= m_count)
|
||||
return false;
|
||||
|
||||
uint16_t temp[4];
|
||||
if (!m_buffer_view->ReadElement(temp, index, sizeof(uint8_t[4])))
|
||||
return false;
|
||||
|
||||
out[0] = static_cast<unsigned>(temp[0]);
|
||||
out[1] = static_cast<unsigned>(temp[1]);
|
||||
out[2] = static_cast<unsigned>(temp[2]);
|
||||
out[3] = static_cast<unsigned>(temp[3]);
|
||||
|
||||
return true;
|
||||
}
|
111
src/ObjLoading/XModel/Gltf/Internal/GltfAccessor.h
Normal file
111
src/ObjLoading/XModel/Gltf/Internal/GltfAccessor.h
Normal file
@ -0,0 +1,111 @@
|
||||
#pragma once
|
||||
#include "GltfBufferView.h"
|
||||
#include "XModel/Gltf/JsonGltf.h"
|
||||
|
||||
namespace gltf
|
||||
{
|
||||
class Accessor
|
||||
{
|
||||
protected:
|
||||
Accessor() = default;
|
||||
|
||||
public:
|
||||
virtual ~Accessor() = default;
|
||||
Accessor(const Accessor& other) = default;
|
||||
Accessor(Accessor&& other) noexcept = default;
|
||||
Accessor& operator=(const Accessor& other) = default;
|
||||
Accessor& operator=(Accessor&& other) noexcept = default;
|
||||
|
||||
[[nodiscard]] virtual std::optional<JsonAccessorType> GetType() const = 0;
|
||||
[[nodiscard]] virtual std::optional<JsonAccessorComponentType> GetComponentType() const = 0;
|
||||
[[nodiscard]] virtual size_t GetCount() const = 0;
|
||||
|
||||
[[nodiscard]] virtual bool GetFloatVec2(size_t index, float (&out)[2]) const = 0;
|
||||
[[nodiscard]] virtual bool GetFloatVec3(size_t index, float (&out)[3]) const = 0;
|
||||
[[nodiscard]] virtual bool GetFloatVec4(size_t index, float (&out)[4]) const = 0;
|
||||
[[nodiscard]] virtual bool GetUnsigned(size_t index, unsigned& out) const = 0;
|
||||
[[nodiscard]] virtual bool GetUnsignedVec4(size_t index, unsigned (&out)[4]) const = 0;
|
||||
};
|
||||
|
||||
class NullAccessor final : public Accessor
|
||||
{
|
||||
public:
|
||||
explicit NullAccessor(size_t count);
|
||||
|
||||
[[nodiscard]] std::optional<JsonAccessorType> GetType() const override;
|
||||
[[nodiscard]] std::optional<JsonAccessorComponentType> GetComponentType() const override;
|
||||
[[nodiscard]] size_t GetCount() const override;
|
||||
|
||||
[[nodiscard]] bool GetFloatVec2(size_t index, float (&out)[2]) const override;
|
||||
[[nodiscard]] bool GetFloatVec3(size_t index, float (&out)[3]) const override;
|
||||
[[nodiscard]] bool GetFloatVec4(size_t index, float (&out)[4]) const override;
|
||||
[[nodiscard]] bool GetUnsigned(size_t index, unsigned& out) const override;
|
||||
[[nodiscard]] bool GetUnsignedVec4(size_t index, unsigned (&out)[4]) const override;
|
||||
|
||||
private:
|
||||
size_t m_count;
|
||||
};
|
||||
|
||||
class FloatAccessor final : public Accessor
|
||||
{
|
||||
public:
|
||||
FloatAccessor(const BufferView* bufferView, JsonAccessorType type, size_t count);
|
||||
|
||||
[[nodiscard]] std::optional<JsonAccessorType> GetType() const override;
|
||||
[[nodiscard]] std::optional<JsonAccessorComponentType> GetComponentType() const override;
|
||||
[[nodiscard]] size_t GetCount() const override;
|
||||
|
||||
[[nodiscard]] bool GetFloatVec2(size_t index, float (&out)[2]) const override;
|
||||
[[nodiscard]] bool GetFloatVec3(size_t index, float (&out)[3]) const override;
|
||||
[[nodiscard]] bool GetFloatVec4(size_t index, float (&out)[4]) const override;
|
||||
[[nodiscard]] bool GetUnsigned(size_t index, unsigned& out) const override;
|
||||
[[nodiscard]] bool GetUnsignedVec4(size_t index, unsigned (&out)[4]) const override;
|
||||
|
||||
private:
|
||||
const BufferView* m_buffer_view;
|
||||
JsonAccessorType m_type;
|
||||
size_t m_count;
|
||||
};
|
||||
|
||||
class UnsignedByteAccessor final : public Accessor
|
||||
{
|
||||
public:
|
||||
UnsignedByteAccessor(const BufferView* bufferView, JsonAccessorType type, size_t count);
|
||||
|
||||
[[nodiscard]] std::optional<JsonAccessorType> GetType() const override;
|
||||
[[nodiscard]] std::optional<JsonAccessorComponentType> GetComponentType() const override;
|
||||
[[nodiscard]] size_t GetCount() const override;
|
||||
|
||||
[[nodiscard]] bool GetFloatVec2(size_t index, float (&out)[2]) const override;
|
||||
[[nodiscard]] bool GetFloatVec3(size_t index, float (&out)[3]) const override;
|
||||
[[nodiscard]] bool GetFloatVec4(size_t index, float (&out)[4]) const override;
|
||||
[[nodiscard]] bool GetUnsigned(size_t index, unsigned& out) const override;
|
||||
[[nodiscard]] bool GetUnsignedVec4(size_t index, unsigned (&out)[4]) const override;
|
||||
|
||||
private:
|
||||
const BufferView* m_buffer_view;
|
||||
JsonAccessorType m_type;
|
||||
size_t m_count;
|
||||
};
|
||||
|
||||
class UnsignedShortAccessor final : public Accessor
|
||||
{
|
||||
public:
|
||||
UnsignedShortAccessor(const BufferView* bufferView, JsonAccessorType type, size_t count);
|
||||
|
||||
[[nodiscard]] std::optional<JsonAccessorType> GetType() const override;
|
||||
[[nodiscard]] std::optional<JsonAccessorComponentType> GetComponentType() const override;
|
||||
[[nodiscard]] size_t GetCount() const override;
|
||||
|
||||
[[nodiscard]] bool GetFloatVec2(size_t index, float (&out)[2]) const override;
|
||||
[[nodiscard]] bool GetFloatVec3(size_t index, float (&out)[3]) const override;
|
||||
[[nodiscard]] bool GetFloatVec4(size_t index, float (&out)[4]) const override;
|
||||
[[nodiscard]] bool GetUnsigned(size_t index, unsigned& out) const override;
|
||||
[[nodiscard]] bool GetUnsignedVec4(size_t index, unsigned (&out)[4]) const override;
|
||||
|
||||
private:
|
||||
const BufferView* m_buffer_view;
|
||||
JsonAccessorType m_type;
|
||||
size_t m_count;
|
||||
};
|
||||
} // namespace gltf
|
80
src/ObjLoading/XModel/Gltf/Internal/GltfBuffer.cpp
Normal file
80
src/ObjLoading/XModel/Gltf/Internal/GltfBuffer.cpp
Normal file
@ -0,0 +1,80 @@
|
||||
#include "GltfBuffer.h"
|
||||
|
||||
#include "XModel/Gltf/GltfConstants.h"
|
||||
|
||||
#include <cassert>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
|
||||
#define LTC_NO_PROTOTYPES
|
||||
#include <tomcrypt.h>
|
||||
|
||||
using namespace gltf;
|
||||
|
||||
EmbeddedBuffer::EmbeddedBuffer(const void* data, const size_t dataSize)
|
||||
: m_data(static_cast<const uint8_t*>(data)),
|
||||
m_data_size(dataSize)
|
||||
{
|
||||
}
|
||||
|
||||
bool EmbeddedBuffer::ReadData(void* dest, const size_t offset, const size_t count) const
|
||||
{
|
||||
assert(m_data);
|
||||
assert(m_data_size > 0u);
|
||||
|
||||
if (offset + count > m_data_size)
|
||||
return false;
|
||||
|
||||
std::memcpy(dest, &m_data[offset], count);
|
||||
return true;
|
||||
}
|
||||
|
||||
size_t EmbeddedBuffer::GetSize() const
|
||||
{
|
||||
return m_data_size;
|
||||
}
|
||||
|
||||
DataUriBuffer::DataUriBuffer()
|
||||
: m_data(nullptr),
|
||||
m_data_size(0u)
|
||||
{
|
||||
}
|
||||
|
||||
bool DataUriBuffer::IsDataUri(const std::string& uri)
|
||||
{
|
||||
return uri.starts_with(GLTF_DATA_URI_PREFIX) && uri.size() > URI_PREFIX_LENGTH;
|
||||
}
|
||||
|
||||
bool DataUriBuffer::ReadDataFromUri(const std::string& uri)
|
||||
{
|
||||
if (!IsDataUri(uri))
|
||||
return false;
|
||||
|
||||
const auto base64DataLength = uri.size() - URI_PREFIX_LENGTH;
|
||||
|
||||
unsigned long outLength = base64DataLength / 4u;
|
||||
m_data = std::make_unique<uint8_t[]>(outLength);
|
||||
const auto result = base64_decode(&uri[URI_PREFIX_LENGTH], base64DataLength, m_data.get(), &outLength);
|
||||
m_data_size = static_cast<size_t>(outLength);
|
||||
|
||||
assert(result == CRYPT_OK);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool DataUriBuffer::ReadData(void* dest, const size_t offset, const size_t count) const
|
||||
{
|
||||
assert(m_data);
|
||||
assert(m_data_size > 0u);
|
||||
|
||||
if (offset + count > m_data_size)
|
||||
return false;
|
||||
|
||||
std::memcpy(dest, &m_data[offset], count);
|
||||
return true;
|
||||
}
|
||||
|
||||
size_t DataUriBuffer::GetSize() const
|
||||
{
|
||||
return m_data_size;
|
||||
}
|
54
src/ObjLoading/XModel/Gltf/Internal/GltfBuffer.h
Normal file
54
src/ObjLoading/XModel/Gltf/Internal/GltfBuffer.h
Normal file
@ -0,0 +1,54 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
namespace gltf
|
||||
{
|
||||
class Buffer
|
||||
{
|
||||
protected:
|
||||
Buffer() = default;
|
||||
|
||||
public:
|
||||
virtual ~Buffer() = default;
|
||||
Buffer(const Buffer& other) = default;
|
||||
Buffer(Buffer&& other) noexcept = default;
|
||||
Buffer& operator=(const Buffer& other) = default;
|
||||
Buffer& operator=(Buffer&& other) noexcept = default;
|
||||
|
||||
virtual bool ReadData(void* dest, size_t offset, size_t count) const = 0;
|
||||
[[nodiscard]] virtual size_t GetSize() const = 0;
|
||||
};
|
||||
|
||||
class EmbeddedBuffer final : public Buffer
|
||||
{
|
||||
public:
|
||||
EmbeddedBuffer(const void* data, size_t dataSize);
|
||||
|
||||
bool ReadData(void* dest, size_t offset, size_t count) const override;
|
||||
[[nodiscard]] size_t GetSize() const override;
|
||||
|
||||
private:
|
||||
const uint8_t* m_data;
|
||||
size_t m_data_size;
|
||||
};
|
||||
|
||||
class DataUriBuffer final : public Buffer
|
||||
{
|
||||
public:
|
||||
DataUriBuffer();
|
||||
|
||||
static bool IsDataUri(const std::string& uri);
|
||||
bool ReadDataFromUri(const std::string& uri);
|
||||
|
||||
bool ReadData(void* dest, size_t offset, size_t count) const override;
|
||||
[[nodiscard]] size_t GetSize() const override;
|
||||
|
||||
private:
|
||||
std::unique_ptr<uint8_t[]> m_data;
|
||||
size_t m_data_size;
|
||||
};
|
||||
} // namespace gltf
|
23
src/ObjLoading/XModel/Gltf/Internal/GltfBufferView.cpp
Normal file
23
src/ObjLoading/XModel/Gltf/Internal/GltfBufferView.cpp
Normal file
@ -0,0 +1,23 @@
|
||||
#include "GltfBufferView.h"
|
||||
|
||||
using namespace gltf;
|
||||
|
||||
BufferView::BufferView(const Buffer* buffer, const size_t offset, const size_t length, const size_t stride)
|
||||
: m_buffer(buffer),
|
||||
m_offset(offset),
|
||||
m_length(length),
|
||||
m_stride(stride)
|
||||
{
|
||||
}
|
||||
|
||||
bool BufferView::ReadElement(void* dest, const size_t elementIndex, const size_t elementSize) const
|
||||
{
|
||||
const auto stride = std::max(elementSize, m_stride);
|
||||
const auto bufferViewOffset = elementIndex * stride;
|
||||
if (bufferViewOffset + elementSize > m_length)
|
||||
return false;
|
||||
|
||||
const auto bufferOffset = m_offset + bufferViewOffset;
|
||||
|
||||
return m_buffer->ReadData(dest, bufferOffset, elementSize);
|
||||
}
|
19
src/ObjLoading/XModel/Gltf/Internal/GltfBufferView.h
Normal file
19
src/ObjLoading/XModel/Gltf/Internal/GltfBufferView.h
Normal file
@ -0,0 +1,19 @@
|
||||
#pragma once
|
||||
#include "GltfBuffer.h"
|
||||
|
||||
namespace gltf
|
||||
{
|
||||
class BufferView
|
||||
{
|
||||
public:
|
||||
BufferView(const Buffer* buffer, size_t offset, size_t length, size_t stride);
|
||||
|
||||
bool ReadElement(void* dest, size_t elementIndex, size_t elementSize) const;
|
||||
|
||||
private:
|
||||
const Buffer* m_buffer;
|
||||
size_t m_offset;
|
||||
size_t m_length;
|
||||
size_t m_stride;
|
||||
};
|
||||
} // namespace gltf
|
18
src/ObjLoading/XModel/XModelLoader.h
Normal file
18
src/ObjLoading/XModel/XModelLoader.h
Normal file
@ -0,0 +1,18 @@
|
||||
#pragma once
|
||||
|
||||
#include "XModel/XModelCommon.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
class XModelLoader
|
||||
{
|
||||
public:
|
||||
XModelLoader() = default;
|
||||
virtual ~XModelLoader() = default;
|
||||
XModelLoader(const XModelLoader& other) = default;
|
||||
XModelLoader(XModelLoader&& other) noexcept = default;
|
||||
XModelLoader& operator=(const XModelLoader& other) = default;
|
||||
XModelLoader& operator=(XModelLoader&& other) noexcept = default;
|
||||
|
||||
virtual std::unique_ptr<XModelCommon> Load() = 0;
|
||||
};
|
Reference in New Issue
Block a user