chore: implement mesh gltf export

This commit is contained in:
Jan 2024-04-27 10:35:00 +02:00
parent a39e993cc6
commit 07fa12b7f6
No known key found for this signature in database
GPG Key ID: 44B581F78FF5C57C
4 changed files with 251 additions and 10 deletions

View File

@ -18,4 +18,10 @@ namespace gltf
constexpr auto GLTF_JSON_CHUNK_DATA_OFFSET = 20u;
constexpr auto GLTF_DATA_URI_PREFIX = "data:application/octet-stream;base64,";
constexpr auto GLTF_ATTRIBUTE_POSITION = "POSITION";
constexpr auto GLTF_ATTRIBUTE_NORMAL = "NORMAL";
constexpr auto GLTF_ATTRIBUTE_TEXCOORD_0 = "TEXCOORD_0";
constexpr auto GLTF_ATTRIBUTE_JOINTS_0 = "JOINTS_0";
constexpr auto GLTF_ATTRIBUTE_WEIGHTS_0 = "WEIGHTS_0";
} // namespace gltf

View File

@ -102,16 +102,29 @@ namespace gltf
NLOHMANN_DEFINE_TYPE_EXTENSION(JsonAccessor, bufferView, byteOffset, componentType, count, type, min, max);
enum class JsonBufferViewTarget
{
ARRAY_BUFFER = 34962,
ELEMENT_ARRAY_BUFFER = 34963
};
NLOHMANN_JSON_SERIALIZE_ENUM(JsonBufferViewTarget,
{
{JsonBufferViewTarget::ARRAY_BUFFER, static_cast<unsigned>(JsonBufferViewTarget::ARRAY_BUFFER) },
{JsonBufferViewTarget::ELEMENT_ARRAY_BUFFER, static_cast<unsigned>(JsonBufferViewTarget::ELEMENT_ARRAY_BUFFER)},
});
class JsonBufferView
{
public:
unsigned buffer;
unsigned byteLength;
unsigned byteOffset;
unsigned target;
std::optional<unsigned> byteOffset;
std::optional<unsigned> byteStride;
std::optional<JsonBufferViewTarget> target;
};
NLOHMANN_DEFINE_TYPE_EXTENSION(JsonBufferView, buffer, byteLength, byteOffset, target);
NLOHMANN_DEFINE_TYPE_EXTENSION(JsonBufferView, buffer, byteLength, byteOffset, byteStride, target);
enum class JsonAnimationChannelTargetPath
{
@ -211,10 +224,23 @@ namespace gltf
{JsonMeshPrimitivesMode::TRIANGLE_FAN, static_cast<unsigned>(JsonMeshPrimitivesMode::TRIANGLE_FAN) },
});
// This should probably be a map, but the supported models do not have arbitrary primitives anyway
class JsonMeshPrimitivesAttributes
{
public:
std::optional<unsigned> POSITION;
std::optional<unsigned> NORMAL;
std::optional<unsigned> TEXCOORD_0;
std::optional<unsigned> JOINTS_0;
std::optional<unsigned> WEIGHTS_0;
};
NLOHMANN_DEFINE_TYPE_EXTENSION(JsonMeshPrimitivesAttributes, POSITION, NORMAL, TEXCOORD_0, JOINTS_0, WEIGHTS_0);
class JsonMeshPrimitives
{
public:
std::unordered_map<std::string, unsigned> attributes;
JsonMeshPrimitivesAttributes attributes;
std::optional<unsigned> indices;
std::optional<unsigned> material;
std::optional<JsonMeshPrimitivesMode> mode;

View File

@ -5,6 +5,8 @@
#include <iomanip>
#include <nlohmann/json.hpp>
#define LTC_NO_PROTOTYPES
#include <tomcrypt.h>
using namespace gltf;
@ -17,16 +19,17 @@ TextOutput::TextOutput(std::ostream& stream)
std::optional<std::string> TextOutput::CreateBufferUri(const void* buffer, const size_t bufferSize) const
{
static constexpr auto URI_PREFIX_LENGTH = std::char_traits<char>::length(GLTF_DATA_URI_PREFIX);
const auto base64Length = utils::Align(4u * (bufferSize / 3u), 4u);
const auto base64Length = 4u * ((bufferSize + 2u) / 3u);
const auto base64BufferSize = URI_PREFIX_LENGTH + base64Length;
std::string output(base64BufferSize, '\0');
std::memcpy(output.data(), GLTF_DATA_URI_PREFIX, URI_PREFIX_LENGTH);
unsigned long outLength = base64Length;
base64_encode(static_cast<const unsigned char*>(buffer), bufferSize, &output[URI_PREFIX_LENGTH], &outLength);
unsigned long outLength = base64Length + 1u;
const auto result = base64_encode(static_cast<const unsigned char*>(buffer), bufferSize, &output[URI_PREFIX_LENGTH], &outLength);
assert(result == CRYPT_OK);
assert(outLength == base64Length);
return output;

View File

@ -11,6 +11,13 @@ namespace
{
constexpr auto GLTF_GENERATOR = "OpenAssetTools " GIT_VERSION;
struct GltfVertex
{
float coordinates[3];
float normal[3];
float uv[2];
};
class GltfWriterImpl final : public gltf::Writer
{
static constexpr auto NODE_INDEX_MESH = 0u;
@ -27,15 +34,26 @@ namespace
void Write(const XModelCommon& xmodel) override
{
JsonRoot gltf;
std::vector<uint8_t> bufferData;
CreateJsonAsset(gltf.asset);
CreateMeshNode(gltf, xmodel);
CreateMesh(gltf, xmodel);
CreateMaterials(gltf, xmodel);
CreateSkeletonNodes(gltf, xmodel);
CreateSkin(gltf, xmodel);
CreateBufferViews(gltf, xmodel);
CreateAccessors(gltf, xmodel);
CreateMesh(gltf, xmodel);
CreateScene(gltf, xmodel);
FillBufferData(xmodel, bufferData);
CreateBuffer(gltf, xmodel, bufferData);
const json jRoot = gltf;
m_output->EmitJson(jRoot);
if (!bufferData.empty())
m_output->EmitBuffer(bufferData.data(), bufferData.size());
m_output->Finalize();
}
@ -66,16 +84,50 @@ namespace
gltf.nodes->emplace_back(std::move(meshNode));
}
static void CreateMesh(JsonRoot& gltf, const XModelCommon& xmodel)
void CreateMesh(JsonRoot& gltf, const XModelCommon& xmodel)
{
if (!gltf.meshes.has_value())
gltf.meshes.emplace();
JsonMesh mesh;
auto objectIndex = 0u;
for (const auto& object : xmodel.m_objects)
{
JsonMeshPrimitives primitives;
if (object.materialIndex >= 0)
primitives.material = static_cast<unsigned>(object.materialIndex);
primitives.attributes.POSITION = m_position_accessor;
primitives.attributes.NORMAL = m_normal_accessor;
primitives.attributes.TEXCOORD_0 = m_uv_accessor;
primitives.mode = JsonMeshPrimitivesMode::TRIANGLES;
primitives.indices = m_first_index_accessor + objectIndex;
mesh.primitives.emplace_back(primitives);
objectIndex++;
}
gltf.meshes->emplace_back(std::move(mesh));
}
static void CreateMaterials(JsonRoot& gltf, const XModelCommon& xmodel)
{
if (!gltf.materials.has_value())
gltf.materials.emplace();
for (const auto& modelMaterial : xmodel.m_materials)
{
JsonMaterial material;
material.name = modelMaterial.name;
gltf.materials->emplace_back(material);
}
}
static void CreateSkeletonNodes(JsonRoot& gltf, const XModelCommon& xmodel)
{
if (xmodel.m_bones.empty())
@ -98,7 +150,7 @@ namespace
for (auto maybeChildIndex = 0u; maybeChildIndex < boneCount; maybeChildIndex++)
{
if (xmodel.m_bones[maybeChildIndex].parentIndex == static_cast<int>(boneIndex))
children.emplace_back(boneIndex + NODE_FIRST_INDEX_BONES);
children.emplace_back(maybeChildIndex + NODE_FIRST_INDEX_BONES);
}
if (!children.empty())
boneNode.children = std::move(children);
@ -143,7 +195,161 @@ namespace
gltf.scene = 0u;
}
void CreateBufferViews(JsonRoot& gltf, const XModelCommon& xmodel)
{
if (!gltf.bufferViews.has_value())
gltf.bufferViews.emplace();
unsigned bufferOffset = 0u;
JsonBufferView vertexBufferView;
vertexBufferView.buffer = 0u;
vertexBufferView.byteOffset = bufferOffset;
vertexBufferView.byteStride = sizeof(GltfVertex);
vertexBufferView.byteLength = sizeof(GltfVertex) * xmodel.m_vertices.size();
vertexBufferView.target = JsonBufferViewTarget::ARRAY_BUFFER;
bufferOffset += vertexBufferView.byteLength;
m_vertex_buffer_view = gltf.bufferViews->size();
gltf.bufferViews->emplace_back(vertexBufferView);
m_first_index_buffer_view = gltf.bufferViews->size();
for (const auto& object : xmodel.m_objects)
{
JsonBufferView indicesBufferView;
indicesBufferView.buffer = 0u;
indicesBufferView.byteOffset = bufferOffset;
indicesBufferView.byteLength = sizeof(unsigned short) * object.m_faces.size() * 3u;
indicesBufferView.target = JsonBufferViewTarget::ELEMENT_ARRAY_BUFFER;
bufferOffset += indicesBufferView.byteLength;
gltf.bufferViews->emplace_back(indicesBufferView);
}
}
void CreateAccessors(JsonRoot& gltf, const XModelCommon& xmodel)
{
if (!gltf.accessors.has_value())
gltf.accessors.emplace();
JsonAccessor positionAccessor;
positionAccessor.bufferView = m_vertex_buffer_view;
positionAccessor.byteOffset = offsetof(GltfVertex, coordinates);
positionAccessor.componentType = JsonAccessorComponentType::FLOAT;
positionAccessor.count = xmodel.m_vertices.size();
positionAccessor.type = JsonAccessorType::VEC3;
m_position_accessor = gltf.accessors->size();
gltf.accessors->emplace_back(positionAccessor);
JsonAccessor normalAccessor;
normalAccessor.bufferView = m_vertex_buffer_view;
normalAccessor.byteOffset = offsetof(GltfVertex, normal);
normalAccessor.componentType = JsonAccessorComponentType::FLOAT;
normalAccessor.count = xmodel.m_vertices.size();
normalAccessor.type = JsonAccessorType::VEC3;
m_normal_accessor = gltf.accessors->size();
gltf.accessors->emplace_back(normalAccessor);
JsonAccessor uvAccessor;
uvAccessor.bufferView = m_vertex_buffer_view;
uvAccessor.byteOffset = offsetof(GltfVertex, uv);
uvAccessor.componentType = JsonAccessorComponentType::FLOAT;
uvAccessor.count = xmodel.m_vertices.size();
uvAccessor.type = JsonAccessorType::VEC2;
m_uv_accessor = gltf.accessors->size();
gltf.accessors->emplace_back(uvAccessor);
m_first_index_accessor = gltf.accessors->size();
for (auto i = 0u; i < xmodel.m_objects.size(); i++)
{
const auto& object = xmodel.m_objects[i];
JsonAccessor indicesAccessor;
indicesAccessor.bufferView = m_first_index_buffer_view + i;
indicesAccessor.componentType = JsonAccessorComponentType::UNSIGNED_SHORT;
indicesAccessor.count = object.m_faces.size() * 3u;
indicesAccessor.type = JsonAccessorType::SCALAR;
gltf.accessors->emplace_back(indicesAccessor);
}
}
static void FillBufferData(const XModelCommon& xmodel, std::vector<uint8_t>& bufferData)
{
const auto expectedBufferSize = GetExpectedBufferSize(xmodel);
bufferData.resize(expectedBufferSize);
auto currentBufferOffset = 0u;
for (const auto& commonVertex : xmodel.m_vertices)
{
auto* vertex = reinterpret_cast<GltfVertex*>(&bufferData[currentBufferOffset]);
vertex->coordinates[0] = commonVertex.coordinates[0];
vertex->coordinates[1] = commonVertex.coordinates[2];
vertex->coordinates[2] = -commonVertex.coordinates[1];
vertex->normal[0] = commonVertex.normal[0];
vertex->normal[1] = commonVertex.normal[2];
vertex->normal[2] = -commonVertex.normal[1];
vertex->uv[0] = commonVertex.uv[0];
vertex->uv[1] = commonVertex.uv[1];
currentBufferOffset += sizeof(GltfVertex);
}
for (const auto& object : xmodel.m_objects)
{
for (const auto& face : object.m_faces)
{
auto* faceIndices = reinterpret_cast<unsigned short*>(&bufferData[currentBufferOffset]);
faceIndices[0] = static_cast<unsigned short>(face.vertexIndex[2]);
faceIndices[1] = static_cast<unsigned short>(face.vertexIndex[1]);
faceIndices[2] = static_cast<unsigned short>(face.vertexIndex[0]);
currentBufferOffset += sizeof(unsigned short) * 3u;
}
}
assert(expectedBufferSize == currentBufferOffset);
}
static size_t GetExpectedBufferSize(const XModelCommon& xmodel)
{
auto result = 0u;
result += xmodel.m_vertices.size() * sizeof(GltfVertex);
for (const auto& object : xmodel.m_objects)
{
result += object.m_faces.size() * sizeof(unsigned short) * 3u;
}
return result;
}
void CreateBuffer(JsonRoot& gltf, const XModelCommon& xmodel, const std::vector<uint8_t>& bufferData) const
{
if (!gltf.buffers.has_value())
gltf.buffers.emplace();
JsonBuffer jsonBuffer;
jsonBuffer.byteLength = bufferData.size();
if (!bufferData.empty())
jsonBuffer.uri = m_output->CreateBufferUri(bufferData.data(), bufferData.size());
gltf.buffers->emplace_back(std::move(jsonBuffer));
}
unsigned m_skeleton_node = 0u;
unsigned m_position_accessor = 0u;
unsigned m_normal_accessor = 0u;
unsigned m_uv_accessor = 0u;
unsigned m_vertex_buffer_view = 0u;
unsigned m_first_index_buffer_view = 0u;
unsigned m_first_index_accessor = 0u;
const Output* m_output;
std::string m_game_name;