mirror of
https://github.com/Laupetin/OpenAssetTools.git
synced 2025-04-20 16:15:43 +00:00
chore: implement mesh gltf export
This commit is contained in:
parent
a39e993cc6
commit
07fa12b7f6
@ -18,4 +18,10 @@ namespace gltf
|
|||||||
constexpr auto GLTF_JSON_CHUNK_DATA_OFFSET = 20u;
|
constexpr auto GLTF_JSON_CHUNK_DATA_OFFSET = 20u;
|
||||||
|
|
||||||
constexpr auto GLTF_DATA_URI_PREFIX = "data:application/octet-stream;base64,";
|
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
|
} // namespace gltf
|
||||||
|
@ -102,16 +102,29 @@ namespace gltf
|
|||||||
|
|
||||||
NLOHMANN_DEFINE_TYPE_EXTENSION(JsonAccessor, bufferView, byteOffset, componentType, count, type, min, max);
|
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
|
class JsonBufferView
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
unsigned buffer;
|
unsigned buffer;
|
||||||
unsigned byteLength;
|
unsigned byteLength;
|
||||||
unsigned byteOffset;
|
std::optional<unsigned> byteOffset;
|
||||||
unsigned target;
|
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
|
enum class JsonAnimationChannelTargetPath
|
||||||
{
|
{
|
||||||
@ -211,10 +224,23 @@ namespace gltf
|
|||||||
{JsonMeshPrimitivesMode::TRIANGLE_FAN, static_cast<unsigned>(JsonMeshPrimitivesMode::TRIANGLE_FAN) },
|
{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
|
class JsonMeshPrimitives
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
std::unordered_map<std::string, unsigned> attributes;
|
JsonMeshPrimitivesAttributes attributes;
|
||||||
std::optional<unsigned> indices;
|
std::optional<unsigned> indices;
|
||||||
std::optional<unsigned> material;
|
std::optional<unsigned> material;
|
||||||
std::optional<JsonMeshPrimitivesMode> mode;
|
std::optional<JsonMeshPrimitivesMode> mode;
|
||||||
|
@ -5,6 +5,8 @@
|
|||||||
|
|
||||||
#include <iomanip>
|
#include <iomanip>
|
||||||
#include <nlohmann/json.hpp>
|
#include <nlohmann/json.hpp>
|
||||||
|
|
||||||
|
#define LTC_NO_PROTOTYPES
|
||||||
#include <tomcrypt.h>
|
#include <tomcrypt.h>
|
||||||
|
|
||||||
using namespace gltf;
|
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
|
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);
|
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;
|
const auto base64BufferSize = URI_PREFIX_LENGTH + base64Length;
|
||||||
|
|
||||||
std::string output(base64BufferSize, '\0');
|
std::string output(base64BufferSize, '\0');
|
||||||
|
|
||||||
std::memcpy(output.data(), GLTF_DATA_URI_PREFIX, URI_PREFIX_LENGTH);
|
std::memcpy(output.data(), GLTF_DATA_URI_PREFIX, URI_PREFIX_LENGTH);
|
||||||
|
|
||||||
unsigned long outLength = base64Length;
|
unsigned long outLength = base64Length + 1u;
|
||||||
base64_encode(static_cast<const unsigned char*>(buffer), bufferSize, &output[URI_PREFIX_LENGTH], &outLength);
|
const auto result = base64_encode(static_cast<const unsigned char*>(buffer), bufferSize, &output[URI_PREFIX_LENGTH], &outLength);
|
||||||
|
|
||||||
|
assert(result == CRYPT_OK);
|
||||||
assert(outLength == base64Length);
|
assert(outLength == base64Length);
|
||||||
|
|
||||||
return output;
|
return output;
|
||||||
|
@ -11,6 +11,13 @@ namespace
|
|||||||
{
|
{
|
||||||
constexpr auto GLTF_GENERATOR = "OpenAssetTools " GIT_VERSION;
|
constexpr auto GLTF_GENERATOR = "OpenAssetTools " GIT_VERSION;
|
||||||
|
|
||||||
|
struct GltfVertex
|
||||||
|
{
|
||||||
|
float coordinates[3];
|
||||||
|
float normal[3];
|
||||||
|
float uv[2];
|
||||||
|
};
|
||||||
|
|
||||||
class GltfWriterImpl final : public gltf::Writer
|
class GltfWriterImpl final : public gltf::Writer
|
||||||
{
|
{
|
||||||
static constexpr auto NODE_INDEX_MESH = 0u;
|
static constexpr auto NODE_INDEX_MESH = 0u;
|
||||||
@ -27,15 +34,26 @@ namespace
|
|||||||
void Write(const XModelCommon& xmodel) override
|
void Write(const XModelCommon& xmodel) override
|
||||||
{
|
{
|
||||||
JsonRoot gltf;
|
JsonRoot gltf;
|
||||||
|
std::vector<uint8_t> bufferData;
|
||||||
|
|
||||||
CreateJsonAsset(gltf.asset);
|
CreateJsonAsset(gltf.asset);
|
||||||
CreateMeshNode(gltf, xmodel);
|
CreateMeshNode(gltf, xmodel);
|
||||||
CreateMesh(gltf, xmodel);
|
CreateMaterials(gltf, xmodel);
|
||||||
CreateSkeletonNodes(gltf, xmodel);
|
CreateSkeletonNodes(gltf, xmodel);
|
||||||
CreateSkin(gltf, xmodel);
|
CreateSkin(gltf, xmodel);
|
||||||
|
CreateBufferViews(gltf, xmodel);
|
||||||
|
CreateAccessors(gltf, xmodel);
|
||||||
|
CreateMesh(gltf, xmodel);
|
||||||
CreateScene(gltf, xmodel);
|
CreateScene(gltf, xmodel);
|
||||||
|
FillBufferData(xmodel, bufferData);
|
||||||
|
CreateBuffer(gltf, xmodel, bufferData);
|
||||||
|
|
||||||
const json jRoot = gltf;
|
const json jRoot = gltf;
|
||||||
m_output->EmitJson(jRoot);
|
m_output->EmitJson(jRoot);
|
||||||
|
|
||||||
|
if (!bufferData.empty())
|
||||||
|
m_output->EmitBuffer(bufferData.data(), bufferData.size());
|
||||||
|
|
||||||
m_output->Finalize();
|
m_output->Finalize();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -66,16 +84,50 @@ namespace
|
|||||||
gltf.nodes->emplace_back(std::move(meshNode));
|
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())
|
if (!gltf.meshes.has_value())
|
||||||
gltf.meshes.emplace();
|
gltf.meshes.emplace();
|
||||||
|
|
||||||
JsonMesh mesh;
|
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));
|
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)
|
static void CreateSkeletonNodes(JsonRoot& gltf, const XModelCommon& xmodel)
|
||||||
{
|
{
|
||||||
if (xmodel.m_bones.empty())
|
if (xmodel.m_bones.empty())
|
||||||
@ -98,7 +150,7 @@ namespace
|
|||||||
for (auto maybeChildIndex = 0u; maybeChildIndex < boneCount; maybeChildIndex++)
|
for (auto maybeChildIndex = 0u; maybeChildIndex < boneCount; maybeChildIndex++)
|
||||||
{
|
{
|
||||||
if (xmodel.m_bones[maybeChildIndex].parentIndex == static_cast<int>(boneIndex))
|
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())
|
if (!children.empty())
|
||||||
boneNode.children = std::move(children);
|
boneNode.children = std::move(children);
|
||||||
@ -143,7 +195,161 @@ namespace
|
|||||||
gltf.scene = 0u;
|
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_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;
|
const Output* m_output;
|
||||||
std::string m_game_name;
|
std::string m_game_name;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user